blob: 29458ca9cdb6db8fad90da2d78ec369693ec748d [file] [log] [blame]
Ed Tanous904063f2017-03-02 16:48:24 -08001/**
Ed Tanous4758d5b2017-06-06 15:28:13 -07002 * @license AngularJS v1.6.4
3 * (c) 2010-2017 Google, Inc. http://angularjs.org
Ed Tanous904063f2017-03-02 16:48:24 -08004 * License: MIT
5 */
6(function(window) {'use strict';
7
8/**
9 * @description
10 *
11 * This object provides a utility for producing rich Error messages within
12 * Angular. It can be called as follows:
13 *
14 * var exampleMinErr = minErr('example');
15 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
16 *
17 * The above creates an instance of minErr in the example namespace. The
18 * resulting error will have a namespaced error code of example.one. The
19 * resulting error will replace {0} with the value of foo, and {1} with the
20 * value of bar. The object is not restricted in the number of arguments it can
21 * take.
22 *
23 * If fewer arguments are specified than necessary for interpolation, the extra
24 * interpolation markers will be preserved in the final string.
25 *
26 * Since data will be parsed statically during a build step, some restrictions
27 * are applied with respect to how minErr instances are created and called.
28 * Instances should have names of the form namespaceMinErr for a minErr created
29 * using minErr('namespace') . Error codes, namespaces and template strings
30 * should all be static strings, not variables or general expressions.
31 *
32 * @param {string} module The namespace to use for the new minErr instance.
33 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
34 * error from returned function, for cases when a particular type of error is useful.
35 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
36 */
37
38function minErr(module, ErrorConstructor) {
39 ErrorConstructor = ErrorConstructor || Error;
40 return function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070041 var code = arguments[0],
42 template = arguments[1],
Ed Tanous904063f2017-03-02 16:48:24 -080043 message = '[' + (module ? module + ':' : '') + code + '] ',
Ed Tanous4758d5b2017-06-06 15:28:13 -070044 templateArgs = sliceArgs(arguments, 2).map(function(arg) {
45 return toDebugString(arg, minErrConfig.objectMaxDepth);
46 }),
Ed Tanous904063f2017-03-02 16:48:24 -080047 paramPrefix, i;
48
49 message += template.replace(/\{\d+\}/g, function(match) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070050 var index = +match.slice(1, -1);
Ed Tanous904063f2017-03-02 16:48:24 -080051
Ed Tanous4758d5b2017-06-06 15:28:13 -070052 if (index < templateArgs.length) {
53 return templateArgs[index];
Ed Tanous904063f2017-03-02 16:48:24 -080054 }
55
56 return match;
57 });
58
Ed Tanous4758d5b2017-06-06 15:28:13 -070059 message += '\nhttp://errors.angularjs.org/1.6.4/' +
Ed Tanous904063f2017-03-02 16:48:24 -080060 (module ? module + '/' : '') + code;
61
Ed Tanous4758d5b2017-06-06 15:28:13 -070062 for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
63 message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
Ed Tanous904063f2017-03-02 16:48:24 -080064 }
65
66 return new ErrorConstructor(message);
67 };
68}
69
Ed Tanous4758d5b2017-06-06 15:28:13 -070070/* We need to tell ESLint what variables are being exported */
71/* exported
72 angular,
73 msie,
74 jqLite,
75 jQuery,
76 slice,
77 splice,
78 push,
79 toString,
80 minErrConfig,
81 errorHandlingConfig,
82 isValidObjectMaxDepth,
83 ngMinErr,
84 angularModule,
85 uid,
86 REGEX_STRING_REGEXP,
87 VALIDITY_STATE_PROPERTY,
Ed Tanous904063f2017-03-02 16:48:24 -080088
Ed Tanous4758d5b2017-06-06 15:28:13 -070089 lowercase,
90 uppercase,
91 manualLowercase,
92 manualUppercase,
93 nodeName_,
94 isArrayLike,
95 forEach,
96 forEachSorted,
97 reverseParams,
98 nextUid,
99 setHashKey,
100 extend,
101 toInt,
102 inherit,
103 merge,
104 noop,
105 identity,
106 valueFn,
107 isUndefined,
108 isDefined,
109 isObject,
110 isBlankObject,
111 isString,
112 isNumber,
113 isNumberNaN,
114 isDate,
115 isArray,
116 isFunction,
117 isRegExp,
118 isWindow,
119 isScope,
120 isFile,
121 isFormData,
122 isBlob,
123 isBoolean,
124 isPromiseLike,
125 trim,
126 escapeForRegexp,
127 isElement,
128 makeMap,
129 includes,
130 arrayRemove,
131 copy,
132 simpleCompare,
133 equals,
134 csp,
135 jq,
136 concat,
137 sliceArgs,
138 bind,
139 toJsonReplacer,
140 toJson,
141 fromJson,
142 convertTimezoneToLocal,
143 timezoneToOffset,
144 startingTag,
145 tryDecodeURIComponent,
146 parseKeyValue,
147 toKeyValue,
148 encodeUriSegment,
149 encodeUriQuery,
150 angularInit,
151 bootstrap,
152 getTestability,
153 snake_case,
154 bindJQuery,
155 assertArg,
156 assertArgFn,
157 assertNotHasOwnProperty,
158 getter,
159 getBlockNodes,
160 hasOwnProperty,
161 createMap,
162 stringify,
Ed Tanous904063f2017-03-02 16:48:24 -0800163
Ed Tanous4758d5b2017-06-06 15:28:13 -0700164 NODE_TYPE_ELEMENT,
165 NODE_TYPE_ATTRIBUTE,
166 NODE_TYPE_TEXT,
167 NODE_TYPE_COMMENT,
168 NODE_TYPE_DOCUMENT,
169 NODE_TYPE_DOCUMENT_FRAGMENT
Ed Tanous904063f2017-03-02 16:48:24 -0800170*/
171
172////////////////////////////////////
173
174/**
175 * @ngdoc module
176 * @name ng
177 * @module ng
178 * @installation
179 * @description
180 *
181 * # ng (core module)
182 * The ng module is loaded by default when an AngularJS application is started. The module itself
183 * contains the essential components for an AngularJS application to function. The table below
184 * lists a high level breakdown of each of the services/factories, filters, directives and testing
185 * components available within this core module.
186 *
187 * <div doc-module-components="ng"></div>
188 */
189
190var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
191
192// The name of a form control's ValidityState property.
193// This is used so that it's possible for internal tests to create mock ValidityStates.
194var VALIDITY_STATE_PROPERTY = 'validity';
195
Ed Tanous4758d5b2017-06-06 15:28:13 -0700196
Ed Tanous904063f2017-03-02 16:48:24 -0800197var hasOwnProperty = Object.prototype.hasOwnProperty;
198
Ed Tanous4758d5b2017-06-06 15:28:13 -0700199var minErrConfig = {
200 objectMaxDepth: 5
201};
202
203 /**
204 * @ngdoc function
205 * @name angular.errorHandlingConfig
206 * @module ng
207 * @kind function
208 *
209 * @description
210 * Configure several aspects of error handling in AngularJS if used as a setter or return the
211 * current configuration if used as a getter. The following options are supported:
212 *
213 * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages.
214 *
215 * Omitted or undefined options will leave the corresponding configuration values unchanged.
216 *
217 * @param {Object=} config - The configuration object. May only contain the options that need to be
218 * updated. Supported keys:
219 *
220 * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
221 * non-positive or non-numeric value, removes the max depth limit.
222 * Default: 5
223 */
224function errorHandlingConfig(config) {
225 if (isObject(config)) {
226 if (isDefined(config.objectMaxDepth)) {
227 minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
228 }
229 } else {
230 return minErrConfig;
231 }
232}
233
234/**
235 * @private
236 * @param {Number} maxDepth
237 * @return {boolean}
238 */
239function isValidObjectMaxDepth(maxDepth) {
240 return isNumber(maxDepth) && maxDepth > 0;
241}
242
243/**
244 * @ngdoc function
245 * @name angular.lowercase
246 * @module ng
247 * @kind function
248 *
249 * @deprecated
250 * sinceVersion="1.5.0"
251 * removeVersion="1.7.0"
252 * Use [String.prototype.toLowerCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) instead.
253 *
254 * @description Converts the specified string to lowercase.
255 * @param {string} string String to be converted to lowercase.
256 * @returns {string} Lowercased string.
257 */
Ed Tanous904063f2017-03-02 16:48:24 -0800258var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
Ed Tanous4758d5b2017-06-06 15:28:13 -0700259
260/**
261 * @ngdoc function
262 * @name angular.uppercase
263 * @module ng
264 * @kind function
265 *
266 * @deprecated
267 * sinceVersion="1.5.0"
268 * removeVersion="1.7.0"
269 * Use [String.prototype.toUpperCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) instead.
270 *
271 * @description Converts the specified string to uppercase.
272 * @param {string} string String to be converted to uppercase.
273 * @returns {string} Uppercased string.
274 */
Ed Tanous904063f2017-03-02 16:48:24 -0800275var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
276
277
278var manualLowercase = function(s) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700279 /* eslint-disable no-bitwise */
Ed Tanous904063f2017-03-02 16:48:24 -0800280 return isString(s)
281 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
282 : s;
Ed Tanous4758d5b2017-06-06 15:28:13 -0700283 /* eslint-enable */
Ed Tanous904063f2017-03-02 16:48:24 -0800284};
285var manualUppercase = function(s) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700286 /* eslint-disable no-bitwise */
Ed Tanous904063f2017-03-02 16:48:24 -0800287 return isString(s)
288 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
289 : s;
Ed Tanous4758d5b2017-06-06 15:28:13 -0700290 /* eslint-enable */
Ed Tanous904063f2017-03-02 16:48:24 -0800291};
292
293
294// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
295// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
296// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387
297if ('i' !== 'I'.toLowerCase()) {
298 lowercase = manualLowercase;
299 uppercase = manualUppercase;
300}
301
302
303var
304 msie, // holds major version number for IE, or NaN if UA is not IE.
305 jqLite, // delay binding since jQuery could be loaded after us.
306 jQuery, // delay binding
307 slice = [].slice,
308 splice = [].splice,
309 push = [].push,
310 toString = Object.prototype.toString,
311 getPrototypeOf = Object.getPrototypeOf,
312 ngMinErr = minErr('ng'),
313
314 /** @name angular */
315 angular = window.angular || (window.angular = {}),
316 angularModule,
317 uid = 0;
318
Ed Tanous4758d5b2017-06-06 15:28:13 -0700319// Support: IE 9-11 only
Ed Tanous904063f2017-03-02 16:48:24 -0800320/**
321 * documentMode is an IE-only property
322 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
323 */
324msie = window.document.documentMode;
325
326
327/**
328 * @private
329 * @param {*} obj
330 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
331 * String ...)
332 */
333function isArrayLike(obj) {
334
335 // `null`, `undefined` and `window` are not array-like
336 if (obj == null || isWindow(obj)) return false;
337
338 // arrays, strings and jQuery/jqLite objects are array like
339 // * jqLite is either the jQuery or jqLite constructor function
340 // * we have to check the existence of jqLite first as this method is called
341 // via the forEach method when constructing the jqLite object in the first place
342 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
343
344 // Support: iOS 8.2 (not reproducible in simulator)
345 // "length" in obj used to prevent JIT error (gh-11508)
Ed Tanous4758d5b2017-06-06 15:28:13 -0700346 var length = 'length' in Object(obj) && obj.length;
Ed Tanous904063f2017-03-02 16:48:24 -0800347
348 // NodeList objects (with `item` method) and
349 // other objects with suitable length characteristics are array-like
350 return isNumber(length) &&
Ed Tanous4758d5b2017-06-06 15:28:13 -0700351 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function');
Ed Tanous904063f2017-03-02 16:48:24 -0800352
353}
354
355/**
356 * @ngdoc function
357 * @name angular.forEach
358 * @module ng
359 * @kind function
360 *
361 * @description
362 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
363 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
364 * is the value of an object property or an array element, `key` is the object property key or
365 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
366 *
367 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
368 * using the `hasOwnProperty` method.
369 *
370 * Unlike ES262's
371 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
372 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
373 * return the value provided.
374 *
375 ```js
376 var values = {name: 'misko', gender: 'male'};
377 var log = [];
378 angular.forEach(values, function(value, key) {
379 this.push(key + ': ' + value);
380 }, log);
381 expect(log).toEqual(['name: misko', 'gender: male']);
382 ```
383 *
384 * @param {Object|Array} obj Object to iterate over.
385 * @param {Function} iterator Iterator function.
386 * @param {Object=} context Object to become context (`this`) for the iterator function.
387 * @returns {Object|Array} Reference to `obj`.
388 */
389
390function forEach(obj, iterator, context) {
391 var key, length;
392 if (obj) {
393 if (isFunction(obj)) {
394 for (key in obj) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700395 if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) {
Ed Tanous904063f2017-03-02 16:48:24 -0800396 iterator.call(context, obj[key], key, obj);
397 }
398 }
399 } else if (isArray(obj) || isArrayLike(obj)) {
400 var isPrimitive = typeof obj !== 'object';
401 for (key = 0, length = obj.length; key < length; key++) {
402 if (isPrimitive || key in obj) {
403 iterator.call(context, obj[key], key, obj);
404 }
405 }
406 } else if (obj.forEach && obj.forEach !== forEach) {
407 obj.forEach(iterator, context, obj);
408 } else if (isBlankObject(obj)) {
409 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
410 for (key in obj) {
411 iterator.call(context, obj[key], key, obj);
412 }
413 } else if (typeof obj.hasOwnProperty === 'function') {
414 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
415 for (key in obj) {
416 if (obj.hasOwnProperty(key)) {
417 iterator.call(context, obj[key], key, obj);
418 }
419 }
420 } else {
421 // Slow path for objects which do not have a method `hasOwnProperty`
422 for (key in obj) {
423 if (hasOwnProperty.call(obj, key)) {
424 iterator.call(context, obj[key], key, obj);
425 }
426 }
427 }
428 }
429 return obj;
430}
431
432function forEachSorted(obj, iterator, context) {
433 var keys = Object.keys(obj).sort();
434 for (var i = 0; i < keys.length; i++) {
435 iterator.call(context, obj[keys[i]], keys[i]);
436 }
437 return keys;
438}
439
440
441/**
442 * when using forEach the params are value, key, but it is often useful to have key, value.
443 * @param {function(string, *)} iteratorFn
444 * @returns {function(*, string)}
445 */
446function reverseParams(iteratorFn) {
447 return function(value, key) {iteratorFn(key, value);};
448}
449
450/**
451 * A consistent way of creating unique IDs in angular.
452 *
453 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
454 * we hit number precision issues in JavaScript.
455 *
456 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
457 *
458 * @returns {number} an unique alpha-numeric string
459 */
460function nextUid() {
461 return ++uid;
462}
463
464
465/**
466 * Set or clear the hashkey for an object.
467 * @param obj object
468 * @param h the hashkey (!truthy to delete the hashkey)
469 */
470function setHashKey(obj, h) {
471 if (h) {
472 obj.$$hashKey = h;
473 } else {
474 delete obj.$$hashKey;
475 }
476}
477
478
479function baseExtend(dst, objs, deep) {
480 var h = dst.$$hashKey;
481
482 for (var i = 0, ii = objs.length; i < ii; ++i) {
483 var obj = objs[i];
484 if (!isObject(obj) && !isFunction(obj)) continue;
485 var keys = Object.keys(obj);
486 for (var j = 0, jj = keys.length; j < jj; j++) {
487 var key = keys[j];
488 var src = obj[key];
489
490 if (deep && isObject(src)) {
491 if (isDate(src)) {
492 dst[key] = new Date(src.valueOf());
493 } else if (isRegExp(src)) {
494 dst[key] = new RegExp(src);
495 } else if (src.nodeName) {
496 dst[key] = src.cloneNode(true);
497 } else if (isElement(src)) {
498 dst[key] = src.clone();
499 } else {
500 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
501 baseExtend(dst[key], [src], true);
502 }
503 } else {
504 dst[key] = src;
505 }
506 }
507 }
508
509 setHashKey(dst, h);
510 return dst;
511}
512
513/**
514 * @ngdoc function
515 * @name angular.extend
516 * @module ng
517 * @kind function
518 *
519 * @description
520 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
521 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
522 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
523 *
524 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
525 * {@link angular.merge} for this.
526 *
527 * @param {Object} dst Destination object.
528 * @param {...Object} src Source object(s).
529 * @returns {Object} Reference to `dst`.
530 */
531function extend(dst) {
532 return baseExtend(dst, slice.call(arguments, 1), false);
533}
534
535
536/**
537* @ngdoc function
538* @name angular.merge
539* @module ng
540* @kind function
541*
542* @description
543* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
544* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
545* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
546*
547* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
548* objects, performing a deep copy.
549*
550* @param {Object} dst Destination object.
551* @param {...Object} src Source object(s).
552* @returns {Object} Reference to `dst`.
553*/
554function merge(dst) {
555 return baseExtend(dst, slice.call(arguments, 1), true);
556}
557
558
559
560function toInt(str) {
561 return parseInt(str, 10);
562}
563
Ed Tanous4758d5b2017-06-06 15:28:13 -0700564var isNumberNaN = Number.isNaN || function isNumberNaN(num) {
565 // eslint-disable-next-line no-self-compare
566 return num !== num;
567};
568
Ed Tanous904063f2017-03-02 16:48:24 -0800569
570function inherit(parent, extra) {
571 return extend(Object.create(parent), extra);
572}
573
574/**
575 * @ngdoc function
576 * @name angular.noop
577 * @module ng
578 * @kind function
579 *
580 * @description
581 * A function that performs no operations. This function can be useful when writing code in the
582 * functional style.
583 ```js
584 function foo(callback) {
585 var result = calculateResult();
586 (callback || angular.noop)(result);
587 }
588 ```
589 */
590function noop() {}
591noop.$inject = [];
592
593
594/**
595 * @ngdoc function
596 * @name angular.identity
597 * @module ng
598 * @kind function
599 *
600 * @description
601 * A function that returns its first argument. This function is useful when writing code in the
602 * functional style.
603 *
604 ```js
605 function transformer(transformationFn, value) {
606 return (transformationFn || angular.identity)(value);
607 };
608
609 // E.g.
610 function getResult(fn, input) {
611 return (fn || angular.identity)(input);
612 };
613
614 getResult(function(n) { return n * 2; }, 21); // returns 42
615 getResult(null, 21); // returns 21
616 getResult(undefined, 21); // returns 21
617 ```
618 *
619 * @param {*} value to be returned.
620 * @returns {*} the value passed in.
621 */
622function identity($) {return $;}
623identity.$inject = [];
624
625
626function valueFn(value) {return function valueRef() {return value;};}
627
628function hasCustomToString(obj) {
629 return isFunction(obj.toString) && obj.toString !== toString;
630}
631
632
633/**
634 * @ngdoc function
635 * @name angular.isUndefined
636 * @module ng
637 * @kind function
638 *
639 * @description
640 * Determines if a reference is undefined.
641 *
642 * @param {*} value Reference to check.
643 * @returns {boolean} True if `value` is undefined.
644 */
645function isUndefined(value) {return typeof value === 'undefined';}
646
647
648/**
649 * @ngdoc function
650 * @name angular.isDefined
651 * @module ng
652 * @kind function
653 *
654 * @description
655 * Determines if a reference is defined.
656 *
657 * @param {*} value Reference to check.
658 * @returns {boolean} True if `value` is defined.
659 */
660function isDefined(value) {return typeof value !== 'undefined';}
661
662
663/**
664 * @ngdoc function
665 * @name angular.isObject
666 * @module ng
667 * @kind function
668 *
669 * @description
670 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
671 * considered to be objects. Note that JavaScript arrays are objects.
672 *
673 * @param {*} value Reference to check.
674 * @returns {boolean} True if `value` is an `Object` but not `null`.
675 */
676function isObject(value) {
677 // http://jsperf.com/isobject4
678 return value !== null && typeof value === 'object';
679}
680
681
682/**
683 * Determine if a value is an object with a null prototype
684 *
685 * @returns {boolean} True if `value` is an `Object` with a null prototype
686 */
687function isBlankObject(value) {
688 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
689}
690
691
692/**
693 * @ngdoc function
694 * @name angular.isString
695 * @module ng
696 * @kind function
697 *
698 * @description
699 * Determines if a reference is a `String`.
700 *
701 * @param {*} value Reference to check.
702 * @returns {boolean} True if `value` is a `String`.
703 */
704function isString(value) {return typeof value === 'string';}
705
706
707/**
708 * @ngdoc function
709 * @name angular.isNumber
710 * @module ng
711 * @kind function
712 *
713 * @description
714 * Determines if a reference is a `Number`.
715 *
716 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
717 *
718 * If you wish to exclude these then you can use the native
719 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
720 * method.
721 *
722 * @param {*} value Reference to check.
723 * @returns {boolean} True if `value` is a `Number`.
724 */
725function isNumber(value) {return typeof value === 'number';}
726
727
728/**
729 * @ngdoc function
730 * @name angular.isDate
731 * @module ng
732 * @kind function
733 *
734 * @description
735 * Determines if a value is a date.
736 *
737 * @param {*} value Reference to check.
738 * @returns {boolean} True if `value` is a `Date`.
739 */
740function isDate(value) {
741 return toString.call(value) === '[object Date]';
742}
743
744
745/**
746 * @ngdoc function
747 * @name angular.isArray
748 * @module ng
749 * @kind function
750 *
751 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -0700752 * Determines if a reference is an `Array`. Alias of Array.isArray.
Ed Tanous904063f2017-03-02 16:48:24 -0800753 *
754 * @param {*} value Reference to check.
755 * @returns {boolean} True if `value` is an `Array`.
756 */
757var isArray = Array.isArray;
758
759/**
760 * @ngdoc function
761 * @name angular.isFunction
762 * @module ng
763 * @kind function
764 *
765 * @description
766 * Determines if a reference is a `Function`.
767 *
768 * @param {*} value Reference to check.
769 * @returns {boolean} True if `value` is a `Function`.
770 */
771function isFunction(value) {return typeof value === 'function';}
772
773
774/**
775 * Determines if a value is a regular expression object.
776 *
777 * @private
778 * @param {*} value Reference to check.
779 * @returns {boolean} True if `value` is a `RegExp`.
780 */
781function isRegExp(value) {
782 return toString.call(value) === '[object RegExp]';
783}
784
785
786/**
787 * Checks if `obj` is a window object.
788 *
789 * @private
790 * @param {*} obj Object to check
791 * @returns {boolean} True if `obj` is a window obj.
792 */
793function isWindow(obj) {
794 return obj && obj.window === obj;
795}
796
797
798function isScope(obj) {
799 return obj && obj.$evalAsync && obj.$watch;
800}
801
802
803function isFile(obj) {
804 return toString.call(obj) === '[object File]';
805}
806
807
808function isFormData(obj) {
809 return toString.call(obj) === '[object FormData]';
810}
811
812
813function isBlob(obj) {
814 return toString.call(obj) === '[object Blob]';
815}
816
817
818function isBoolean(value) {
819 return typeof value === 'boolean';
820}
821
822
823function isPromiseLike(obj) {
824 return obj && isFunction(obj.then);
825}
826
827
Ed Tanous4758d5b2017-06-06 15:28:13 -0700828var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/;
Ed Tanous904063f2017-03-02 16:48:24 -0800829function isTypedArray(value) {
830 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
831}
832
833function isArrayBuffer(obj) {
834 return toString.call(obj) === '[object ArrayBuffer]';
835}
836
837
838var trim = function(value) {
839 return isString(value) ? value.trim() : value;
840};
841
842// Copied from:
843// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
844// Prereq: s is a string.
845var escapeForRegexp = function(s) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700846 return s
847 .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1')
848 // eslint-disable-next-line no-control-regex
849 .replace(/\x08/g, '\\x08');
Ed Tanous904063f2017-03-02 16:48:24 -0800850};
851
852
853/**
854 * @ngdoc function
855 * @name angular.isElement
856 * @module ng
857 * @kind function
858 *
859 * @description
860 * Determines if a reference is a DOM element (or wrapped jQuery element).
861 *
862 * @param {*} value Reference to check.
863 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
864 */
865function isElement(node) {
866 return !!(node &&
867 (node.nodeName // We are a direct element.
868 || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API.
869}
870
871/**
872 * @param str 'key1,key2,...'
873 * @returns {object} in the form of {key1:true, key2:true, ...}
874 */
875function makeMap(str) {
876 var obj = {}, items = str.split(','), i;
877 for (i = 0; i < items.length; i++) {
878 obj[items[i]] = true;
879 }
880 return obj;
881}
882
883
884function nodeName_(element) {
885 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
886}
887
888function includes(array, obj) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700889 return Array.prototype.indexOf.call(array, obj) !== -1;
Ed Tanous904063f2017-03-02 16:48:24 -0800890}
891
892function arrayRemove(array, value) {
893 var index = array.indexOf(value);
894 if (index >= 0) {
895 array.splice(index, 1);
896 }
897 return index;
898}
899
900/**
901 * @ngdoc function
902 * @name angular.copy
903 * @module ng
904 * @kind function
905 *
906 * @description
907 * Creates a deep copy of `source`, which should be an object or an array.
908 *
909 * * If no destination is supplied, a copy of the object or array is created.
910 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
911 * are deleted and then all elements/properties from the source are copied to it.
912 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
913 * * If `source` is identical to `destination` an exception will be thrown.
914 *
915 * <br />
916 * <div class="alert alert-warning">
917 * Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
918 * and on `destination`) will be ignored.
919 * </div>
920 *
921 * @param {*} source The source that will be used to make a copy.
922 * Can be any type, including primitives, `null`, and `undefined`.
923 * @param {(Object|Array)=} destination Destination into which the source is copied. If
924 * provided, must be of the same type as `source`.
925 * @returns {*} The copy or updated `destination`, if `destination` was specified.
926 *
927 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -0700928 <example module="copyExample" name="angular-copy">
Ed Tanous904063f2017-03-02 16:48:24 -0800929 <file name="index.html">
930 <div ng-controller="ExampleController">
931 <form novalidate class="simple-form">
932 <label>Name: <input type="text" ng-model="user.name" /></label><br />
933 <label>Age: <input type="number" ng-model="user.age" /></label><br />
934 Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
935 <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
936 <button ng-click="reset()">RESET</button>
937 <button ng-click="update(user)">SAVE</button>
938 </form>
939 <pre>form = {{user | json}}</pre>
940 <pre>master = {{master | json}}</pre>
941 </div>
942 </file>
943 <file name="script.js">
944 // Module: copyExample
945 angular.
946 module('copyExample', []).
947 controller('ExampleController', ['$scope', function($scope) {
948 $scope.master = {};
949
950 $scope.reset = function() {
951 // Example with 1 argument
952 $scope.user = angular.copy($scope.master);
953 };
954
955 $scope.update = function(user) {
956 // Example with 2 arguments
957 angular.copy(user, $scope.master);
958 };
959
960 $scope.reset();
961 }]);
962 </file>
963 </example>
964 */
Ed Tanous4758d5b2017-06-06 15:28:13 -0700965function copy(source, destination, maxDepth) {
Ed Tanous904063f2017-03-02 16:48:24 -0800966 var stackSource = [];
967 var stackDest = [];
Ed Tanous4758d5b2017-06-06 15:28:13 -0700968 maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN;
Ed Tanous904063f2017-03-02 16:48:24 -0800969
970 if (destination) {
971 if (isTypedArray(destination) || isArrayBuffer(destination)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700972 throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.');
Ed Tanous904063f2017-03-02 16:48:24 -0800973 }
974 if (source === destination) {
Ed Tanous4758d5b2017-06-06 15:28:13 -0700975 throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.');
Ed Tanous904063f2017-03-02 16:48:24 -0800976 }
977
978 // Empty the destination object
979 if (isArray(destination)) {
980 destination.length = 0;
981 } else {
982 forEach(destination, function(value, key) {
983 if (key !== '$$hashKey') {
984 delete destination[key];
985 }
986 });
987 }
988
989 stackSource.push(source);
990 stackDest.push(destination);
Ed Tanous4758d5b2017-06-06 15:28:13 -0700991 return copyRecurse(source, destination, maxDepth);
Ed Tanous904063f2017-03-02 16:48:24 -0800992 }
993
Ed Tanous4758d5b2017-06-06 15:28:13 -0700994 return copyElement(source, maxDepth);
Ed Tanous904063f2017-03-02 16:48:24 -0800995
Ed Tanous4758d5b2017-06-06 15:28:13 -0700996 function copyRecurse(source, destination, maxDepth) {
997 maxDepth--;
998 if (maxDepth < 0) {
999 return '...';
1000 }
Ed Tanous904063f2017-03-02 16:48:24 -08001001 var h = destination.$$hashKey;
1002 var key;
1003 if (isArray(source)) {
1004 for (var i = 0, ii = source.length; i < ii; i++) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001005 destination.push(copyElement(source[i], maxDepth));
Ed Tanous904063f2017-03-02 16:48:24 -08001006 }
1007 } else if (isBlankObject(source)) {
1008 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
1009 for (key in source) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001010 destination[key] = copyElement(source[key], maxDepth);
Ed Tanous904063f2017-03-02 16:48:24 -08001011 }
1012 } else if (source && typeof source.hasOwnProperty === 'function') {
1013 // Slow path, which must rely on hasOwnProperty
1014 for (key in source) {
1015 if (source.hasOwnProperty(key)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001016 destination[key] = copyElement(source[key], maxDepth);
Ed Tanous904063f2017-03-02 16:48:24 -08001017 }
1018 }
1019 } else {
1020 // Slowest path --- hasOwnProperty can't be called as a method
1021 for (key in source) {
1022 if (hasOwnProperty.call(source, key)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001023 destination[key] = copyElement(source[key], maxDepth);
Ed Tanous904063f2017-03-02 16:48:24 -08001024 }
1025 }
1026 }
1027 setHashKey(destination, h);
1028 return destination;
1029 }
1030
Ed Tanous4758d5b2017-06-06 15:28:13 -07001031 function copyElement(source, maxDepth) {
Ed Tanous904063f2017-03-02 16:48:24 -08001032 // Simple values
1033 if (!isObject(source)) {
1034 return source;
1035 }
1036
1037 // Already copied values
1038 var index = stackSource.indexOf(source);
1039 if (index !== -1) {
1040 return stackDest[index];
1041 }
1042
1043 if (isWindow(source) || isScope(source)) {
1044 throw ngMinErr('cpws',
Ed Tanous4758d5b2017-06-06 15:28:13 -07001045 'Can\'t copy! Making copies of Window or Scope instances is not supported.');
Ed Tanous904063f2017-03-02 16:48:24 -08001046 }
1047
1048 var needsRecurse = false;
1049 var destination = copyType(source);
1050
1051 if (destination === undefined) {
1052 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
1053 needsRecurse = true;
1054 }
1055
1056 stackSource.push(source);
1057 stackDest.push(destination);
1058
1059 return needsRecurse
Ed Tanous4758d5b2017-06-06 15:28:13 -07001060 ? copyRecurse(source, destination, maxDepth)
Ed Tanous904063f2017-03-02 16:48:24 -08001061 : destination;
1062 }
1063
1064 function copyType(source) {
1065 switch (toString.call(source)) {
1066 case '[object Int8Array]':
1067 case '[object Int16Array]':
1068 case '[object Int32Array]':
1069 case '[object Float32Array]':
1070 case '[object Float64Array]':
1071 case '[object Uint8Array]':
1072 case '[object Uint8ClampedArray]':
1073 case '[object Uint16Array]':
1074 case '[object Uint32Array]':
1075 return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);
1076
1077 case '[object ArrayBuffer]':
Ed Tanous4758d5b2017-06-06 15:28:13 -07001078 // Support: IE10
Ed Tanous904063f2017-03-02 16:48:24 -08001079 if (!source.slice) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001080 // If we're in this case we know the environment supports ArrayBuffer
1081 /* eslint-disable no-undef */
Ed Tanous904063f2017-03-02 16:48:24 -08001082 var copied = new ArrayBuffer(source.byteLength);
1083 new Uint8Array(copied).set(new Uint8Array(source));
Ed Tanous4758d5b2017-06-06 15:28:13 -07001084 /* eslint-enable */
Ed Tanous904063f2017-03-02 16:48:24 -08001085 return copied;
1086 }
1087 return source.slice(0);
1088
1089 case '[object Boolean]':
1090 case '[object Number]':
1091 case '[object String]':
1092 case '[object Date]':
1093 return new source.constructor(source.valueOf());
1094
1095 case '[object RegExp]':
Ed Tanous4758d5b2017-06-06 15:28:13 -07001096 var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
Ed Tanous904063f2017-03-02 16:48:24 -08001097 re.lastIndex = source.lastIndex;
1098 return re;
1099
1100 case '[object Blob]':
1101 return new source.constructor([source], {type: source.type});
1102 }
1103
1104 if (isFunction(source.cloneNode)) {
1105 return source.cloneNode(true);
1106 }
1107 }
1108}
1109
1110
Ed Tanous4758d5b2017-06-06 15:28:13 -07001111// eslint-disable-next-line no-self-compare
1112function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }
1113
1114
Ed Tanous904063f2017-03-02 16:48:24 -08001115/**
1116 * @ngdoc function
1117 * @name angular.equals
1118 * @module ng
1119 * @kind function
1120 *
1121 * @description
1122 * Determines if two objects or two values are equivalent. Supports value types, regular
1123 * expressions, arrays and objects.
1124 *
1125 * Two objects or values are considered equivalent if at least one of the following is true:
1126 *
1127 * * Both objects or values pass `===` comparison.
1128 * * Both objects or values are of the same type and all of their properties are equal by
1129 * comparing them with `angular.equals`.
1130 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1131 * * Both values represent the same regular expression (In JavaScript,
1132 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1133 * representation matches).
1134 *
1135 * During a property comparison, properties of `function` type and properties with names
1136 * that begin with `$` are ignored.
1137 *
1138 * Scope and DOMWindow objects are being compared only by identify (`===`).
1139 *
1140 * @param {*} o1 Object or value to compare.
1141 * @param {*} o2 Object or value to compare.
1142 * @returns {boolean} True if arguments are equal.
1143 *
1144 * @example
1145 <example module="equalsExample" name="equalsExample">
1146 <file name="index.html">
1147 <div ng-controller="ExampleController">
1148 <form novalidate>
1149 <h3>User 1</h3>
1150 Name: <input type="text" ng-model="user1.name">
1151 Age: <input type="number" ng-model="user1.age">
1152
1153 <h3>User 2</h3>
1154 Name: <input type="text" ng-model="user2.name">
1155 Age: <input type="number" ng-model="user2.age">
1156
1157 <div>
1158 <br/>
1159 <input type="button" value="Compare" ng-click="compare()">
1160 </div>
1161 User 1: <pre>{{user1 | json}}</pre>
1162 User 2: <pre>{{user2 | json}}</pre>
1163 Equal: <pre>{{result}}</pre>
1164 </form>
1165 </div>
1166 </file>
1167 <file name="script.js">
1168 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
1169 $scope.user1 = {};
1170 $scope.user2 = {};
Ed Tanous904063f2017-03-02 16:48:24 -08001171 $scope.compare = function() {
1172 $scope.result = angular.equals($scope.user1, $scope.user2);
1173 };
1174 }]);
1175 </file>
1176 </example>
1177 */
1178function equals(o1, o2) {
1179 if (o1 === o2) return true;
1180 if (o1 === null || o2 === null) return false;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001181 // eslint-disable-next-line no-self-compare
Ed Tanous904063f2017-03-02 16:48:24 -08001182 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1183 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001184 if (t1 === t2 && t1 === 'object') {
Ed Tanous904063f2017-03-02 16:48:24 -08001185 if (isArray(o1)) {
1186 if (!isArray(o2)) return false;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001187 if ((length = o1.length) === o2.length) {
Ed Tanous904063f2017-03-02 16:48:24 -08001188 for (key = 0; key < length; key++) {
1189 if (!equals(o1[key], o2[key])) return false;
1190 }
1191 return true;
1192 }
1193 } else if (isDate(o1)) {
1194 if (!isDate(o2)) return false;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001195 return simpleCompare(o1.getTime(), o2.getTime());
Ed Tanous904063f2017-03-02 16:48:24 -08001196 } else if (isRegExp(o1)) {
1197 if (!isRegExp(o2)) return false;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001198 return o1.toString() === o2.toString();
Ed Tanous904063f2017-03-02 16:48:24 -08001199 } else {
1200 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1201 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1202 keySet = createMap();
1203 for (key in o1) {
1204 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1205 if (!equals(o1[key], o2[key])) return false;
1206 keySet[key] = true;
1207 }
1208 for (key in o2) {
1209 if (!(key in keySet) &&
1210 key.charAt(0) !== '$' &&
1211 isDefined(o2[key]) &&
1212 !isFunction(o2[key])) return false;
1213 }
1214 return true;
1215 }
1216 }
1217 return false;
1218}
1219
1220var csp = function() {
1221 if (!isDefined(csp.rules)) {
1222
1223
1224 var ngCspElement = (window.document.querySelector('[ng-csp]') ||
1225 window.document.querySelector('[data-ng-csp]'));
1226
1227 if (ngCspElement) {
1228 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1229 ngCspElement.getAttribute('data-ng-csp');
1230 csp.rules = {
1231 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1232 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1233 };
1234 } else {
1235 csp.rules = {
1236 noUnsafeEval: noUnsafeEval(),
1237 noInlineStyle: false
1238 };
1239 }
1240 }
1241
1242 return csp.rules;
1243
1244 function noUnsafeEval() {
1245 try {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001246 // eslint-disable-next-line no-new, no-new-func
Ed Tanous904063f2017-03-02 16:48:24 -08001247 new Function('');
Ed Tanous904063f2017-03-02 16:48:24 -08001248 return false;
1249 } catch (e) {
1250 return true;
1251 }
1252 }
1253};
1254
1255/**
1256 * @ngdoc directive
1257 * @module ng
1258 * @name ngJq
1259 *
1260 * @element ANY
1261 * @param {string=} ngJq the name of the library available under `window`
1262 * to be used for angular.element
1263 * @description
1264 * Use this directive to force the angular.element library. This should be
1265 * used to force either jqLite by leaving ng-jq blank or setting the name of
1266 * the jquery variable under window (eg. jQuery).
1267 *
1268 * Since angular looks for this directive when it is loaded (doesn't wait for the
1269 * DOMContentLoaded event), it must be placed on an element that comes before the script
1270 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1271 * others ignored.
1272 *
1273 * @example
1274 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1275 ```html
1276 <!doctype html>
1277 <html ng-app ng-jq>
1278 ...
1279 ...
1280 </html>
1281 ```
1282 * @example
1283 * This example shows how to use a jQuery based library of a different name.
1284 * The library name must be available at the top most 'window'.
1285 ```html
1286 <!doctype html>
1287 <html ng-app ng-jq="jQueryLib">
1288 ...
1289 ...
1290 </html>
1291 ```
1292 */
1293var jq = function() {
1294 if (isDefined(jq.name_)) return jq.name_;
1295 var el;
1296 var i, ii = ngAttrPrefixes.length, prefix, name;
1297 for (i = 0; i < ii; ++i) {
1298 prefix = ngAttrPrefixes[i];
Ed Tanous4758d5b2017-06-06 15:28:13 -07001299 el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]');
1300 if (el) {
Ed Tanous904063f2017-03-02 16:48:24 -08001301 name = el.getAttribute(prefix + 'jq');
1302 break;
1303 }
1304 }
1305
1306 return (jq.name_ = name);
1307};
1308
1309function concat(array1, array2, index) {
1310 return array1.concat(slice.call(array2, index));
1311}
1312
1313function sliceArgs(args, startIndex) {
1314 return slice.call(args, startIndex || 0);
1315}
1316
1317
Ed Tanous904063f2017-03-02 16:48:24 -08001318/**
1319 * @ngdoc function
1320 * @name angular.bind
1321 * @module ng
1322 * @kind function
1323 *
1324 * @description
1325 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1326 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1327 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1328 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1329 *
1330 * @param {Object} self Context which `fn` should be evaluated in.
1331 * @param {function()} fn Function to be bound.
1332 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1333 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1334 */
Ed Tanous904063f2017-03-02 16:48:24 -08001335function bind(self, fn) {
1336 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1337 if (isFunction(fn) && !(fn instanceof RegExp)) {
1338 return curryArgs.length
1339 ? function() {
1340 return arguments.length
1341 ? fn.apply(self, concat(curryArgs, arguments, 0))
1342 : fn.apply(self, curryArgs);
1343 }
1344 : function() {
1345 return arguments.length
1346 ? fn.apply(self, arguments)
1347 : fn.call(self);
1348 };
1349 } else {
1350 // In IE, native methods are not functions so they cannot be bound (note: they don't need to be).
1351 return fn;
1352 }
1353}
1354
1355
1356function toJsonReplacer(key, value) {
1357 var val = value;
1358
1359 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1360 val = undefined;
1361 } else if (isWindow(value)) {
1362 val = '$WINDOW';
1363 } else if (value && window.document === value) {
1364 val = '$DOCUMENT';
1365 } else if (isScope(value)) {
1366 val = '$SCOPE';
1367 }
1368
1369 return val;
1370}
1371
1372
1373/**
1374 * @ngdoc function
1375 * @name angular.toJson
1376 * @module ng
1377 * @kind function
1378 *
1379 * @description
1380 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1381 * stripped since angular uses this notation internally.
1382 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07001383 * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON.
Ed Tanous904063f2017-03-02 16:48:24 -08001384 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1385 * If set to an integer, the JSON output will contain that many spaces per indentation.
1386 * @returns {string|undefined} JSON-ified string representing `obj`.
1387 * @knownIssue
1388 *
1389 * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date`
1390 * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the
1391 * `Date.prototype.toJSON` method as follows:
1392 *
1393 * ```
1394 * var _DatetoJSON = Date.prototype.toJSON;
1395 * Date.prototype.toJSON = function() {
1396 * try {
1397 * return _DatetoJSON.call(this);
1398 * } catch(e) {
1399 * if (e instanceof RangeError) {
1400 * return null;
1401 * }
1402 * throw e;
1403 * }
1404 * };
1405 * ```
1406 *
1407 * See https://github.com/angular/angular.js/pull/14221 for more information.
1408 */
1409function toJson(obj, pretty) {
1410 if (isUndefined(obj)) return undefined;
1411 if (!isNumber(pretty)) {
1412 pretty = pretty ? 2 : null;
1413 }
1414 return JSON.stringify(obj, toJsonReplacer, pretty);
1415}
1416
1417
1418/**
1419 * @ngdoc function
1420 * @name angular.fromJson
1421 * @module ng
1422 * @kind function
1423 *
1424 * @description
1425 * Deserializes a JSON string.
1426 *
1427 * @param {string} json JSON string to deserialize.
1428 * @returns {Object|Array|string|number} Deserialized JSON string.
1429 */
1430function fromJson(json) {
1431 return isString(json)
1432 ? JSON.parse(json)
1433 : json;
1434}
1435
1436
1437var ALL_COLONS = /:/g;
1438function timezoneToOffset(timezone, fallback) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001439 // Support: IE 9-11 only, Edge 13-14+
Ed Tanous904063f2017-03-02 16:48:24 -08001440 // IE/Edge do not "understand" colon (`:`) in timezone
1441 timezone = timezone.replace(ALL_COLONS, '');
1442 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001443 return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
Ed Tanous904063f2017-03-02 16:48:24 -08001444}
1445
1446
1447function addDateMinutes(date, minutes) {
1448 date = new Date(date.getTime());
1449 date.setMinutes(date.getMinutes() + minutes);
1450 return date;
1451}
1452
1453
1454function convertTimezoneToLocal(date, timezone, reverse) {
1455 reverse = reverse ? -1 : 1;
1456 var dateTimezoneOffset = date.getTimezoneOffset();
1457 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
1458 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
1459}
1460
1461
1462/**
1463 * @returns {string} Returns the string representation of the element.
1464 */
1465function startingTag(element) {
1466 element = jqLite(element).clone();
1467 try {
1468 // turns out IE does not let you set .html() on elements which
1469 // are not allowed to have children. So we just ignore it.
1470 element.empty();
Ed Tanous4758d5b2017-06-06 15:28:13 -07001471 } catch (e) { /* empty */ }
Ed Tanous904063f2017-03-02 16:48:24 -08001472 var elemHtml = jqLite('<div>').append(element).html();
1473 try {
1474 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1475 elemHtml.
1476 match(/^(<[^>]+>)/)[1].
Ed Tanous4758d5b2017-06-06 15:28:13 -07001477 replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
Ed Tanous904063f2017-03-02 16:48:24 -08001478 } catch (e) {
1479 return lowercase(elemHtml);
1480 }
1481
1482}
1483
1484
1485/////////////////////////////////////////////////
1486
1487/**
1488 * Tries to decode the URI component without throwing an exception.
1489 *
1490 * @private
1491 * @param str value potential URI component to check.
1492 * @returns {boolean} True if `value` can be decoded
1493 * with the decodeURIComponent function.
1494 */
1495function tryDecodeURIComponent(value) {
1496 try {
1497 return decodeURIComponent(value);
1498 } catch (e) {
1499 // Ignore any invalid uri component.
1500 }
1501}
1502
1503
1504/**
1505 * Parses an escaped url query string into key-value pairs.
1506 * @returns {Object.<string,boolean|Array>}
1507 */
1508function parseKeyValue(/**string*/keyValue) {
1509 var obj = {};
Ed Tanous4758d5b2017-06-06 15:28:13 -07001510 forEach((keyValue || '').split('&'), function(keyValue) {
Ed Tanous904063f2017-03-02 16:48:24 -08001511 var splitPoint, key, val;
1512 if (keyValue) {
1513 key = keyValue = keyValue.replace(/\+/g,'%20');
1514 splitPoint = keyValue.indexOf('=');
1515 if (splitPoint !== -1) {
1516 key = keyValue.substring(0, splitPoint);
1517 val = keyValue.substring(splitPoint + 1);
1518 }
1519 key = tryDecodeURIComponent(key);
1520 if (isDefined(key)) {
1521 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1522 if (!hasOwnProperty.call(obj, key)) {
1523 obj[key] = val;
1524 } else if (isArray(obj[key])) {
1525 obj[key].push(val);
1526 } else {
1527 obj[key] = [obj[key],val];
1528 }
1529 }
1530 }
1531 });
1532 return obj;
1533}
1534
1535function toKeyValue(obj) {
1536 var parts = [];
1537 forEach(obj, function(value, key) {
1538 if (isArray(value)) {
1539 forEach(value, function(arrayValue) {
1540 parts.push(encodeUriQuery(key, true) +
1541 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1542 });
1543 } else {
1544 parts.push(encodeUriQuery(key, true) +
1545 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1546 }
1547 });
1548 return parts.length ? parts.join('&') : '';
1549}
1550
1551
1552/**
1553 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1554 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1555 * segments:
1556 * segment = *pchar
1557 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1558 * pct-encoded = "%" HEXDIG HEXDIG
1559 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1560 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1561 * / "*" / "+" / "," / ";" / "="
1562 */
1563function encodeUriSegment(val) {
1564 return encodeUriQuery(val, true).
1565 replace(/%26/gi, '&').
1566 replace(/%3D/gi, '=').
1567 replace(/%2B/gi, '+');
1568}
1569
1570
1571/**
1572 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1573 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1574 * encoded per http://tools.ietf.org/html/rfc3986:
Ed Tanous4758d5b2017-06-06 15:28:13 -07001575 * query = *( pchar / "/" / "?" )
Ed Tanous904063f2017-03-02 16:48:24 -08001576 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1577 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1578 * pct-encoded = "%" HEXDIG HEXDIG
1579 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1580 * / "*" / "+" / "," / ";" / "="
1581 */
1582function encodeUriQuery(val, pctEncodeSpaces) {
1583 return encodeURIComponent(val).
1584 replace(/%40/gi, '@').
1585 replace(/%3A/gi, ':').
1586 replace(/%24/g, '$').
1587 replace(/%2C/gi, ',').
1588 replace(/%3B/gi, ';').
1589 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1590}
1591
1592var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1593
1594function getNgAttribute(element, ngAttr) {
1595 var attr, i, ii = ngAttrPrefixes.length;
1596 for (i = 0; i < ii; ++i) {
1597 attr = ngAttrPrefixes[i] + ngAttr;
1598 if (isString(attr = element.getAttribute(attr))) {
1599 return attr;
1600 }
1601 }
1602 return null;
1603}
1604
Ed Tanous4758d5b2017-06-06 15:28:13 -07001605function allowAutoBootstrap(document) {
1606 var script = document.currentScript;
1607
1608 if (!script) {
1609 // IE does not have `document.currentScript`
1610 return true;
1611 }
1612
1613 // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack
1614 if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) {
1615 return false;
1616 }
1617
1618 var attributes = script.attributes;
1619 var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')];
1620
1621 return srcs.every(function(src) {
1622 if (!src) {
1623 return true;
1624 }
1625 if (!src.value) {
1626 return false;
1627 }
1628
1629 var link = document.createElement('a');
1630 link.href = src.value;
1631
1632 if (document.location.origin === link.origin) {
1633 // Same-origin resources are always allowed, even for non-whitelisted schemes.
1634 return true;
1635 }
1636 // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web.
1637 // This is to prevent angular.js bundled with browser extensions from being used to bypass the
1638 // content security policy in web pages and other browser extensions.
1639 switch (link.protocol) {
1640 case 'http:':
1641 case 'https:':
1642 case 'ftp:':
1643 case 'blob:':
1644 case 'file:':
1645 case 'data:':
1646 return true;
1647 default:
1648 return false;
1649 }
1650 });
1651}
1652
1653// Cached as it has to run during loading so that document.currentScript is available.
1654var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1655
Ed Tanous904063f2017-03-02 16:48:24 -08001656/**
1657 * @ngdoc directive
1658 * @name ngApp
1659 * @module ng
1660 *
1661 * @element ANY
1662 * @param {angular.Module} ngApp an optional application
1663 * {@link angular.module module} name to load.
1664 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1665 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1666 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1667 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1668 * tracking down the root of these bugs.
1669 *
1670 * @description
1671 *
1672 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1673 * designates the **root element** of the application and is typically placed near the root element
1674 * of the page - e.g. on the `<body>` or `<html>` tags.
1675 *
1676 * There are a few things to keep in mind when using `ngApp`:
1677 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1678 * found in the document will be used to define the root element to auto-bootstrap as an
1679 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1680 * {@link angular.bootstrap} instead.
1681 * - AngularJS applications cannot be nested within each other.
1682 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`.
1683 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and
1684 * {@link ngRoute.ngView `ngView`}.
1685 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1686 * causing animations to stop working and making the injector inaccessible from outside the app.
1687 *
1688 * You can specify an **AngularJS module** to be used as the root module for the application. This
1689 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1690 * should contain the application code needed or have dependencies on other modules that will
1691 * contain the code. See {@link angular.module} for more information.
1692 *
1693 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1694 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1695 * would not be resolved to `3`.
1696 *
1697 * `ngApp` is the easiest, and most common way to bootstrap an application.
1698 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07001699 <example module="ngAppDemo" name="ng-app">
Ed Tanous904063f2017-03-02 16:48:24 -08001700 <file name="index.html">
1701 <div ng-controller="ngAppDemoController">
1702 I can add: {{a}} + {{b}} = {{ a+b }}
1703 </div>
1704 </file>
1705 <file name="script.js">
1706 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1707 $scope.a = 1;
1708 $scope.b = 2;
1709 });
1710 </file>
1711 </example>
1712 *
1713 * Using `ngStrictDi`, you would see something like this:
1714 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07001715 <example ng-app-included="true" name="strict-di">
Ed Tanous904063f2017-03-02 16:48:24 -08001716 <file name="index.html">
1717 <div ng-app="ngAppStrictDemo" ng-strict-di>
1718 <div ng-controller="GoodController1">
1719 I can add: {{a}} + {{b}} = {{ a+b }}
1720
1721 <p>This renders because the controller does not fail to
1722 instantiate, by using explicit annotation style (see
1723 script.js for details)
1724 </p>
1725 </div>
1726
1727 <div ng-controller="GoodController2">
1728 Name: <input ng-model="name"><br />
1729 Hello, {{name}}!
1730
1731 <p>This renders because the controller does not fail to
1732 instantiate, by using explicit annotation style
1733 (see script.js for details)
1734 </p>
1735 </div>
1736
1737 <div ng-controller="BadController">
1738 I can add: {{a}} + {{b}} = {{ a+b }}
1739
1740 <p>The controller could not be instantiated, due to relying
1741 on automatic function annotations (which are disabled in
1742 strict mode). As such, the content of this section is not
1743 interpolated, and there should be an error in your web console.
1744 </p>
1745 </div>
1746 </div>
1747 </file>
1748 <file name="script.js">
1749 angular.module('ngAppStrictDemo', [])
1750 // BadController will fail to instantiate, due to relying on automatic function annotation,
1751 // rather than an explicit annotation
1752 .controller('BadController', function($scope) {
1753 $scope.a = 1;
1754 $scope.b = 2;
1755 })
1756 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1757 // due to using explicit annotations using the array style and $inject property, respectively.
1758 .controller('GoodController1', ['$scope', function($scope) {
1759 $scope.a = 1;
1760 $scope.b = 2;
1761 }])
1762 .controller('GoodController2', GoodController2);
1763 function GoodController2($scope) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001764 $scope.name = 'World';
Ed Tanous904063f2017-03-02 16:48:24 -08001765 }
1766 GoodController2.$inject = ['$scope'];
1767 </file>
1768 <file name="style.css">
1769 div[ng-controller] {
1770 margin-bottom: 1em;
1771 -webkit-border-radius: 4px;
1772 border-radius: 4px;
1773 border: 1px solid;
1774 padding: .5em;
1775 }
1776 div[ng-controller^=Good] {
1777 border-color: #d6e9c6;
1778 background-color: #dff0d8;
1779 color: #3c763d;
1780 }
1781 div[ng-controller^=Bad] {
1782 border-color: #ebccd1;
1783 background-color: #f2dede;
1784 color: #a94442;
1785 margin-bottom: 0;
1786 }
1787 </file>
1788 </example>
1789 */
1790function angularInit(element, bootstrap) {
1791 var appElement,
1792 module,
1793 config = {};
1794
1795 // The element `element` has priority over any other element.
1796 forEach(ngAttrPrefixes, function(prefix) {
1797 var name = prefix + 'app';
1798
1799 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1800 appElement = element;
1801 module = element.getAttribute(name);
1802 }
1803 });
1804 forEach(ngAttrPrefixes, function(prefix) {
1805 var name = prefix + 'app';
1806 var candidate;
1807
1808 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1809 appElement = candidate;
1810 module = candidate.getAttribute(name);
1811 }
1812 });
1813 if (appElement) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001814 if (!isAutoBootstrapAllowed) {
1815 window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' +
1816 'an extension, document.location.href does not match.');
1817 return;
1818 }
1819 config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
Ed Tanous904063f2017-03-02 16:48:24 -08001820 bootstrap(appElement, module ? [module] : [], config);
1821 }
1822}
1823
1824/**
1825 * @ngdoc function
1826 * @name angular.bootstrap
1827 * @module ng
1828 * @description
1829 * Use this function to manually start up angular application.
1830 *
1831 * For more information, see the {@link guide/bootstrap Bootstrap guide}.
1832 *
1833 * Angular will detect if it has been loaded into the browser more than once and only allow the
1834 * first loaded script to be bootstrapped and will report a warning to the browser console for
1835 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1836 * multiple instances of Angular try to work on the DOM.
1837 *
1838 * <div class="alert alert-warning">
1839 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually.
1840 * They must use {@link ng.directive:ngApp ngApp}.
1841 * </div>
1842 *
1843 * <div class="alert alert-warning">
1844 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion},
1845 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
1846 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1847 * causing animations to stop working and making the injector inaccessible from outside the app.
1848 * </div>
1849 *
1850 * ```html
1851 * <!doctype html>
1852 * <html>
1853 * <body>
1854 * <div ng-controller="WelcomeController">
1855 * {{greeting}}
1856 * </div>
1857 *
1858 * <script src="angular.js"></script>
1859 * <script>
1860 * var app = angular.module('demo', [])
1861 * .controller('WelcomeController', function($scope) {
1862 * $scope.greeting = 'Welcome!';
1863 * });
1864 * angular.bootstrap(document, ['demo']);
1865 * </script>
1866 * </body>
1867 * </html>
1868 * ```
1869 *
1870 * @param {DOMElement} element DOM element which is the root of angular application.
1871 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1872 * Each item in the array should be the name of a predefined module or a (DI annotated)
1873 * function that will be invoked by the injector as a `config` block.
1874 * See: {@link angular.module modules}
1875 * @param {Object=} config an object for defining configuration options for the application. The
1876 * following keys are supported:
1877 *
1878 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1879 * assist in finding bugs which break minified code. Defaults to `false`.
1880 *
1881 * @returns {auto.$injector} Returns the newly created injector for this app.
1882 */
1883function bootstrap(element, modules, config) {
1884 if (!isObject(config)) config = {};
1885 var defaultConfig = {
1886 strictDi: false
1887 };
1888 config = extend(defaultConfig, config);
1889 var doBootstrap = function() {
1890 element = jqLite(element);
1891
1892 if (element.injector()) {
1893 var tag = (element[0] === window.document) ? 'document' : startingTag(element);
1894 // Encode angle brackets to prevent input from being sanitized to empty string #8683.
1895 throw ngMinErr(
1896 'btstrpd',
Ed Tanous4758d5b2017-06-06 15:28:13 -07001897 'App already bootstrapped with this element \'{0}\'',
Ed Tanous904063f2017-03-02 16:48:24 -08001898 tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
1899 }
1900
1901 modules = modules || [];
1902 modules.unshift(['$provide', function($provide) {
1903 $provide.value('$rootElement', element);
1904 }]);
1905
1906 if (config.debugInfoEnabled) {
1907 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1908 modules.push(['$compileProvider', function($compileProvider) {
1909 $compileProvider.debugInfoEnabled(true);
1910 }]);
1911 }
1912
1913 modules.unshift('ng');
1914 var injector = createInjector(modules, config.strictDi);
1915 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1916 function bootstrapApply(scope, element, compile, injector) {
1917 scope.$apply(function() {
1918 element.data('$injector', injector);
1919 compile(element)(scope);
1920 });
1921 }]
1922 );
1923 return injector;
1924 };
1925
1926 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1927 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1928
1929 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1930 config.debugInfoEnabled = true;
1931 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1932 }
1933
1934 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1935 return doBootstrap();
1936 }
1937
1938 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1939 angular.resumeBootstrap = function(extraModules) {
1940 forEach(extraModules, function(module) {
1941 modules.push(module);
1942 });
1943 return doBootstrap();
1944 };
1945
1946 if (isFunction(angular.resumeDeferredBootstrap)) {
1947 angular.resumeDeferredBootstrap();
1948 }
1949}
1950
1951/**
1952 * @ngdoc function
1953 * @name angular.reloadWithDebugInfo
1954 * @module ng
1955 * @description
1956 * Use this function to reload the current application with debug information turned on.
1957 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1958 *
1959 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1960 */
1961function reloadWithDebugInfo() {
1962 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1963 window.location.reload();
1964}
1965
1966/**
1967 * @name angular.getTestability
1968 * @module ng
1969 * @description
1970 * Get the testability service for the instance of Angular on the given
1971 * element.
1972 * @param {DOMElement} element DOM element which is the root of angular application.
1973 */
1974function getTestability(rootElement) {
1975 var injector = angular.element(rootElement).injector();
1976 if (!injector) {
1977 throw ngMinErr('test',
1978 'no injector found for element argument to getTestability');
1979 }
1980 return injector.get('$$testability');
1981}
1982
1983var SNAKE_CASE_REGEXP = /[A-Z]/g;
1984function snake_case(name, separator) {
1985 separator = separator || '_';
1986 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1987 return (pos ? separator : '') + letter.toLowerCase();
1988 });
1989}
1990
1991var bindJQueryFired = false;
1992function bindJQuery() {
1993 var originalCleanData;
1994
1995 if (bindJQueryFired) {
1996 return;
1997 }
1998
1999 // bind to jQuery if present;
2000 var jqName = jq();
2001 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
2002 !jqName ? undefined : // use jqLite
2003 window[jqName]; // use jQuery specified by `ngJq`
2004
2005 // Use jQuery if it exists with proper functionality, otherwise default to us.
2006 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
2007 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
2008 // versions. It will not work for sure with jQuery <1.7, though.
2009 if (jQuery && jQuery.fn.on) {
2010 jqLite = jQuery;
2011 extend(jQuery.fn, {
2012 scope: JQLitePrototype.scope,
2013 isolateScope: JQLitePrototype.isolateScope,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002014 controller: /** @type {?} */ (JQLitePrototype).controller,
Ed Tanous904063f2017-03-02 16:48:24 -08002015 injector: JQLitePrototype.injector,
2016 inheritedData: JQLitePrototype.inheritedData
2017 });
2018
2019 // All nodes removed from the DOM via various jQuery APIs like .remove()
2020 // are passed through jQuery.cleanData. Monkey-patch this method to fire
2021 // the $destroy event on all removed nodes.
2022 originalCleanData = jQuery.cleanData;
2023 jQuery.cleanData = function(elems) {
2024 var events;
2025 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002026 events = jQuery._data(elem, 'events');
Ed Tanous904063f2017-03-02 16:48:24 -08002027 if (events && events.$destroy) {
2028 jQuery(elem).triggerHandler('$destroy');
2029 }
2030 }
2031 originalCleanData(elems);
2032 };
2033 } else {
2034 jqLite = JQLite;
2035 }
2036
2037 angular.element = jqLite;
2038
2039 // Prevent double-proxying.
2040 bindJQueryFired = true;
2041}
2042
2043/**
2044 * throw error if the argument is falsy.
2045 */
2046function assertArg(arg, name, reason) {
2047 if (!arg) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002048 throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required'));
Ed Tanous904063f2017-03-02 16:48:24 -08002049 }
2050 return arg;
2051}
2052
2053function assertArgFn(arg, name, acceptArrayAnnotation) {
2054 if (acceptArrayAnnotation && isArray(arg)) {
2055 arg = arg[arg.length - 1];
2056 }
2057
2058 assertArg(isFunction(arg), name, 'not a function, got ' +
2059 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
2060 return arg;
2061}
2062
2063/**
2064 * throw error if the name given is hasOwnProperty
2065 * @param {String} name the name to test
2066 * @param {String} context the context in which the name is used, such as module or directive
2067 */
2068function assertNotHasOwnProperty(name, context) {
2069 if (name === 'hasOwnProperty') {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002070 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
Ed Tanous904063f2017-03-02 16:48:24 -08002071 }
2072}
2073
2074/**
2075 * Return the value accessible from the object by path. Any undefined traversals are ignored
2076 * @param {Object} obj starting object
2077 * @param {String} path path to traverse
2078 * @param {boolean} [bindFnToScope=true]
2079 * @returns {Object} value as accessible by path
2080 */
2081//TODO(misko): this function needs to be removed
2082function getter(obj, path, bindFnToScope) {
2083 if (!path) return obj;
2084 var keys = path.split('.');
2085 var key;
2086 var lastInstance = obj;
2087 var len = keys.length;
2088
2089 for (var i = 0; i < len; i++) {
2090 key = keys[i];
2091 if (obj) {
2092 obj = (lastInstance = obj)[key];
2093 }
2094 }
2095 if (!bindFnToScope && isFunction(obj)) {
2096 return bind(lastInstance, obj);
2097 }
2098 return obj;
2099}
2100
2101/**
2102 * Return the DOM siblings between the first and last node in the given array.
2103 * @param {Array} array like object
2104 * @returns {Array} the inputted object or a jqLite collection containing the nodes
2105 */
2106function getBlockNodes(nodes) {
2107 // TODO(perf): update `nodes` instead of creating a new object?
2108 var node = nodes[0];
2109 var endNode = nodes[nodes.length - 1];
2110 var blockNodes;
2111
2112 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
2113 if (blockNodes || nodes[i] !== node) {
2114 if (!blockNodes) {
2115 blockNodes = jqLite(slice.call(nodes, 0, i));
2116 }
2117 blockNodes.push(node);
2118 }
2119 }
2120
2121 return blockNodes || nodes;
2122}
2123
2124
2125/**
2126 * Creates a new object without a prototype. This object is useful for lookup without having to
2127 * guard against prototypically inherited properties via hasOwnProperty.
2128 *
2129 * Related micro-benchmarks:
2130 * - http://jsperf.com/object-create2
2131 * - http://jsperf.com/proto-map-lookup/2
2132 * - http://jsperf.com/for-in-vs-object-keys2
2133 *
2134 * @returns {Object}
2135 */
2136function createMap() {
2137 return Object.create(null);
2138}
2139
Ed Tanous4758d5b2017-06-06 15:28:13 -07002140function stringify(value) {
2141 if (value == null) { // null || undefined
2142 return '';
2143 }
2144 switch (typeof value) {
2145 case 'string':
2146 break;
2147 case 'number':
2148 value = '' + value;
2149 break;
2150 default:
2151 if (hasCustomToString(value) && !isArray(value) && !isDate(value)) {
2152 value = value.toString();
2153 } else {
2154 value = toJson(value);
2155 }
2156 }
2157
2158 return value;
2159}
2160
Ed Tanous904063f2017-03-02 16:48:24 -08002161var NODE_TYPE_ELEMENT = 1;
2162var NODE_TYPE_ATTRIBUTE = 2;
2163var NODE_TYPE_TEXT = 3;
2164var NODE_TYPE_COMMENT = 8;
2165var NODE_TYPE_DOCUMENT = 9;
2166var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
2167
2168/**
2169 * @ngdoc type
2170 * @name angular.Module
2171 * @module ng
2172 * @description
2173 *
2174 * Interface for configuring angular {@link angular.module modules}.
2175 */
2176
2177function setupModuleLoader(window) {
2178
2179 var $injectorMinErr = minErr('$injector');
2180 var ngMinErr = minErr('ng');
2181
2182 function ensure(obj, name, factory) {
2183 return obj[name] || (obj[name] = factory());
2184 }
2185
2186 var angular = ensure(window, 'angular', Object);
2187
2188 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2189 angular.$$minErr = angular.$$minErr || minErr;
2190
2191 return ensure(angular, 'module', function() {
2192 /** @type {Object.<string, angular.Module>} */
2193 var modules = {};
2194
2195 /**
2196 * @ngdoc function
2197 * @name angular.module
2198 * @module ng
2199 * @description
2200 *
2201 * The `angular.module` is a global place for creating, registering and retrieving Angular
2202 * modules.
2203 * All modules (angular core or 3rd party) that should be available to an application must be
2204 * registered using this mechanism.
2205 *
2206 * Passing one argument retrieves an existing {@link angular.Module},
2207 * whereas passing more than one argument creates a new {@link angular.Module}
2208 *
2209 *
2210 * # Module
2211 *
2212 * A module is a collection of services, directives, controllers, filters, and configuration information.
2213 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2214 *
2215 * ```js
2216 * // Create a new module
2217 * var myModule = angular.module('myModule', []);
2218 *
2219 * // register a new service
2220 * myModule.value('appName', 'MyCoolApp');
2221 *
2222 * // configure existing services inside initialization blocks.
2223 * myModule.config(['$locationProvider', function($locationProvider) {
2224 * // Configure existing providers
2225 * $locationProvider.hashPrefix('!');
2226 * }]);
2227 * ```
2228 *
2229 * Then you can create an injector and load your modules like this:
2230 *
2231 * ```js
2232 * var injector = angular.injector(['ng', 'myModule'])
2233 * ```
2234 *
2235 * However it's more likely that you'll just use
2236 * {@link ng.directive:ngApp ngApp} or
2237 * {@link angular.bootstrap} to simplify this process for you.
2238 *
2239 * @param {!string} name The name of the module to create or retrieve.
2240 * @param {!Array.<string>=} requires If specified then new module is being created. If
2241 * unspecified then the module is being retrieved for further configuration.
2242 * @param {Function=} configFn Optional configuration function for the module. Same as
2243 * {@link angular.Module#config Module#config()}.
2244 * @returns {angular.Module} new module with the {@link angular.Module} api.
2245 */
2246 return function module(name, requires, configFn) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002247
2248 var info = {};
2249
Ed Tanous904063f2017-03-02 16:48:24 -08002250 var assertNotHasOwnProperty = function(name, context) {
2251 if (name === 'hasOwnProperty') {
2252 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2253 }
2254 };
2255
2256 assertNotHasOwnProperty(name, 'module');
2257 if (requires && modules.hasOwnProperty(name)) {
2258 modules[name] = null;
2259 }
2260 return ensure(modules, name, function() {
2261 if (!requires) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002262 throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' +
2263 'the module name or forgot to load it. If registering a module ensure that you ' +
2264 'specify the dependencies as the second argument.', name);
Ed Tanous904063f2017-03-02 16:48:24 -08002265 }
2266
2267 /** @type {!Array.<Array.<*>>} */
2268 var invokeQueue = [];
2269
2270 /** @type {!Array.<Function>} */
2271 var configBlocks = [];
2272
2273 /** @type {!Array.<Function>} */
2274 var runBlocks = [];
2275
2276 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2277
2278 /** @type {angular.Module} */
2279 var moduleInstance = {
2280 // Private state
2281 _invokeQueue: invokeQueue,
2282 _configBlocks: configBlocks,
2283 _runBlocks: runBlocks,
2284
2285 /**
Ed Tanous4758d5b2017-06-06 15:28:13 -07002286 * @ngdoc method
2287 * @name angular.Module#info
2288 * @module ng
2289 *
2290 * @param {Object=} info Information about the module
2291 * @returns {Object|Module} The current info object for this module if called as a getter,
2292 * or `this` if called as a setter.
2293 *
2294 * @description
2295 * Read and write custom information about this module.
2296 * For example you could put the version of the module in here.
2297 *
2298 * ```js
2299 * angular.module('myModule', []).info({ version: '1.0.0' });
2300 * ```
2301 *
2302 * The version could then be read back out by accessing the module elsewhere:
2303 *
2304 * ```
2305 * var version = angular.module('myModule').info().version;
2306 * ```
2307 *
2308 * You can also retrieve this information during runtime via the
2309 * {@link $injector#modules `$injector.modules`} property:
2310 *
2311 * ```js
2312 * var version = $injector.modules['myModule'].info().version;
2313 * ```
2314 */
2315 info: function(value) {
2316 if (isDefined(value)) {
2317 if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value');
2318 info = value;
2319 return this;
2320 }
2321 return info;
2322 },
2323
2324 /**
Ed Tanous904063f2017-03-02 16:48:24 -08002325 * @ngdoc property
2326 * @name angular.Module#requires
2327 * @module ng
2328 *
2329 * @description
2330 * Holds the list of modules which the injector will load before the current module is
2331 * loaded.
2332 */
2333 requires: requires,
2334
2335 /**
2336 * @ngdoc property
2337 * @name angular.Module#name
2338 * @module ng
2339 *
2340 * @description
2341 * Name of the module.
2342 */
2343 name: name,
2344
2345
2346 /**
2347 * @ngdoc method
2348 * @name angular.Module#provider
2349 * @module ng
2350 * @param {string} name service name
2351 * @param {Function} providerType Construction function for creating new instance of the
2352 * service.
2353 * @description
2354 * See {@link auto.$provide#provider $provide.provider()}.
2355 */
2356 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2357
2358 /**
2359 * @ngdoc method
2360 * @name angular.Module#factory
2361 * @module ng
2362 * @param {string} name service name
2363 * @param {Function} providerFunction Function for creating new instance of the service.
2364 * @description
2365 * See {@link auto.$provide#factory $provide.factory()}.
2366 */
2367 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2368
2369 /**
2370 * @ngdoc method
2371 * @name angular.Module#service
2372 * @module ng
2373 * @param {string} name service name
2374 * @param {Function} constructor A constructor function that will be instantiated.
2375 * @description
2376 * See {@link auto.$provide#service $provide.service()}.
2377 */
2378 service: invokeLaterAndSetModuleName('$provide', 'service'),
2379
2380 /**
2381 * @ngdoc method
2382 * @name angular.Module#value
2383 * @module ng
2384 * @param {string} name service name
2385 * @param {*} object Service instance object.
2386 * @description
2387 * See {@link auto.$provide#value $provide.value()}.
2388 */
2389 value: invokeLater('$provide', 'value'),
2390
2391 /**
2392 * @ngdoc method
2393 * @name angular.Module#constant
2394 * @module ng
2395 * @param {string} name constant name
2396 * @param {*} object Constant value.
2397 * @description
2398 * Because the constants are fixed, they get applied before other provide methods.
2399 * See {@link auto.$provide#constant $provide.constant()}.
2400 */
2401 constant: invokeLater('$provide', 'constant', 'unshift'),
2402
2403 /**
2404 * @ngdoc method
2405 * @name angular.Module#decorator
2406 * @module ng
2407 * @param {string} name The name of the service to decorate.
2408 * @param {Function} decorFn This function will be invoked when the service needs to be
2409 * instantiated and should return the decorated service instance.
2410 * @description
2411 * See {@link auto.$provide#decorator $provide.decorator()}.
2412 */
Ed Tanous4758d5b2017-06-06 15:28:13 -07002413 decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),
Ed Tanous904063f2017-03-02 16:48:24 -08002414
2415 /**
2416 * @ngdoc method
2417 * @name angular.Module#animation
2418 * @module ng
2419 * @param {string} name animation name
2420 * @param {Function} animationFactory Factory function for creating new instance of an
2421 * animation.
2422 * @description
2423 *
2424 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2425 *
2426 *
2427 * Defines an animation hook that can be later used with
2428 * {@link $animate $animate} service and directives that use this service.
2429 *
2430 * ```js
2431 * module.animation('.animation-name', function($inject1, $inject2) {
2432 * return {
2433 * eventName : function(element, done) {
2434 * //code to run the animation
2435 * //once complete, then run done()
2436 * return function cancellationFunction(element) {
2437 * //code to cancel the animation
2438 * }
2439 * }
2440 * }
2441 * })
2442 * ```
2443 *
2444 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2445 * {@link ngAnimate ngAnimate module} for more information.
2446 */
2447 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2448
2449 /**
2450 * @ngdoc method
2451 * @name angular.Module#filter
2452 * @module ng
2453 * @param {string} name Filter name - this must be a valid angular expression identifier
2454 * @param {Function} filterFactory Factory function for creating new instance of filter.
2455 * @description
2456 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2457 *
2458 * <div class="alert alert-warning">
2459 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2460 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2461 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2462 * (`myapp_subsection_filterx`).
2463 * </div>
2464 */
2465 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2466
2467 /**
2468 * @ngdoc method
2469 * @name angular.Module#controller
2470 * @module ng
2471 * @param {string|Object} name Controller name, or an object map of controllers where the
2472 * keys are the names and the values are the constructors.
2473 * @param {Function} constructor Controller constructor function.
2474 * @description
2475 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2476 */
2477 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2478
2479 /**
2480 * @ngdoc method
2481 * @name angular.Module#directive
2482 * @module ng
2483 * @param {string|Object} name Directive name, or an object map of directives where the
2484 * keys are the names and the values are the factories.
2485 * @param {Function} directiveFactory Factory function for creating new instance of
2486 * directives.
2487 * @description
2488 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2489 */
2490 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2491
2492 /**
2493 * @ngdoc method
2494 * @name angular.Module#component
2495 * @module ng
2496 * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
2497 * @param {Object} options Component definition object (a simplified
2498 * {@link ng.$compile#directive-definition-object directive definition object})
2499 *
2500 * @description
2501 * See {@link ng.$compileProvider#component $compileProvider.component()}.
2502 */
2503 component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
2504
2505 /**
2506 * @ngdoc method
2507 * @name angular.Module#config
2508 * @module ng
2509 * @param {Function} configFn Execute this function on module load. Useful for service
2510 * configuration.
2511 * @description
2512 * Use this method to register work which needs to be performed on module loading.
2513 * For more about how to configure services, see
2514 * {@link providers#provider-recipe Provider Recipe}.
2515 */
2516 config: config,
2517
2518 /**
2519 * @ngdoc method
2520 * @name angular.Module#run
2521 * @module ng
2522 * @param {Function} initializationFn Execute this function after injector creation.
2523 * Useful for application initialization.
2524 * @description
2525 * Use this method to register work which should be performed when the injector is done
2526 * loading all modules.
2527 */
2528 run: function(block) {
2529 runBlocks.push(block);
2530 return this;
2531 }
2532 };
2533
2534 if (configFn) {
2535 config(configFn);
2536 }
2537
2538 return moduleInstance;
2539
2540 /**
2541 * @param {string} provider
2542 * @param {string} method
2543 * @param {String=} insertMethod
2544 * @returns {angular.Module}
2545 */
2546 function invokeLater(provider, method, insertMethod, queue) {
2547 if (!queue) queue = invokeQueue;
2548 return function() {
2549 queue[insertMethod || 'push']([provider, method, arguments]);
2550 return moduleInstance;
2551 };
2552 }
2553
2554 /**
2555 * @param {string} provider
2556 * @param {string} method
2557 * @returns {angular.Module}
2558 */
Ed Tanous4758d5b2017-06-06 15:28:13 -07002559 function invokeLaterAndSetModuleName(provider, method, queue) {
2560 if (!queue) queue = invokeQueue;
Ed Tanous904063f2017-03-02 16:48:24 -08002561 return function(recipeName, factoryFunction) {
2562 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
Ed Tanous4758d5b2017-06-06 15:28:13 -07002563 queue.push([provider, method, arguments]);
Ed Tanous904063f2017-03-02 16:48:24 -08002564 return moduleInstance;
2565 };
2566 }
2567 });
2568 };
2569 });
2570
2571}
2572
2573/* global shallowCopy: true */
2574
2575/**
2576 * Creates a shallow copy of an object, an array or a primitive.
2577 *
2578 * Assumes that there are no proto properties for objects.
2579 */
2580function shallowCopy(src, dst) {
2581 if (isArray(src)) {
2582 dst = dst || [];
2583
2584 for (var i = 0, ii = src.length; i < ii; i++) {
2585 dst[i] = src[i];
2586 }
2587 } else if (isObject(src)) {
2588 dst = dst || {};
2589
2590 for (var key in src) {
2591 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
2592 dst[key] = src[key];
2593 }
2594 }
2595 }
2596
2597 return dst || src;
2598}
2599
2600/* global toDebugString: true */
2601
Ed Tanous4758d5b2017-06-06 15:28:13 -07002602function serializeObject(obj, maxDepth) {
Ed Tanous904063f2017-03-02 16:48:24 -08002603 var seen = [];
2604
Ed Tanous4758d5b2017-06-06 15:28:13 -07002605 // There is no direct way to stringify object until reaching a specific depth
2606 // and a very deep object can cause a performance issue, so we copy the object
2607 // based on this specific depth and then stringify it.
2608 if (isValidObjectMaxDepth(maxDepth)) {
2609 obj = copy(obj, null, maxDepth);
2610 }
Ed Tanous904063f2017-03-02 16:48:24 -08002611 return JSON.stringify(obj, function(key, val) {
2612 val = toJsonReplacer(key, val);
2613 if (isObject(val)) {
2614
2615 if (seen.indexOf(val) >= 0) return '...';
2616
2617 seen.push(val);
2618 }
2619 return val;
2620 });
2621}
2622
Ed Tanous4758d5b2017-06-06 15:28:13 -07002623function toDebugString(obj, maxDepth) {
Ed Tanous904063f2017-03-02 16:48:24 -08002624 if (typeof obj === 'function') {
2625 return obj.toString().replace(/ \{[\s\S]*$/, '');
2626 } else if (isUndefined(obj)) {
2627 return 'undefined';
2628 } else if (typeof obj !== 'string') {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002629 return serializeObject(obj, maxDepth);
Ed Tanous904063f2017-03-02 16:48:24 -08002630 }
2631 return obj;
2632}
2633
2634/* global angularModule: true,
2635 version: true,
2636
2637 $CompileProvider,
2638
2639 htmlAnchorDirective,
2640 inputDirective,
2641 inputDirective,
2642 formDirective,
2643 scriptDirective,
2644 selectDirective,
Ed Tanous904063f2017-03-02 16:48:24 -08002645 optionDirective,
2646 ngBindDirective,
2647 ngBindHtmlDirective,
2648 ngBindTemplateDirective,
2649 ngClassDirective,
2650 ngClassEvenDirective,
2651 ngClassOddDirective,
2652 ngCloakDirective,
2653 ngControllerDirective,
2654 ngFormDirective,
2655 ngHideDirective,
2656 ngIfDirective,
2657 ngIncludeDirective,
2658 ngIncludeFillContentDirective,
2659 ngInitDirective,
2660 ngNonBindableDirective,
2661 ngPluralizeDirective,
2662 ngRepeatDirective,
2663 ngShowDirective,
2664 ngStyleDirective,
2665 ngSwitchDirective,
2666 ngSwitchWhenDirective,
2667 ngSwitchDefaultDirective,
2668 ngOptionsDirective,
2669 ngTranscludeDirective,
2670 ngModelDirective,
2671 ngListDirective,
2672 ngChangeDirective,
2673 patternDirective,
2674 patternDirective,
2675 requiredDirective,
2676 requiredDirective,
2677 minlengthDirective,
2678 minlengthDirective,
2679 maxlengthDirective,
2680 maxlengthDirective,
2681 ngValueDirective,
2682 ngModelOptionsDirective,
2683 ngAttributeAliasDirectives,
2684 ngEventDirectives,
2685
2686 $AnchorScrollProvider,
2687 $AnimateProvider,
2688 $CoreAnimateCssProvider,
2689 $$CoreAnimateJsProvider,
2690 $$CoreAnimateQueueProvider,
2691 $$AnimateRunnerFactoryProvider,
2692 $$AnimateAsyncRunFactoryProvider,
2693 $BrowserProvider,
2694 $CacheFactoryProvider,
2695 $ControllerProvider,
2696 $DateProvider,
2697 $DocumentProvider,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002698 $$IsDocumentHiddenProvider,
Ed Tanous904063f2017-03-02 16:48:24 -08002699 $ExceptionHandlerProvider,
2700 $FilterProvider,
2701 $$ForceReflowProvider,
2702 $InterpolateProvider,
2703 $IntervalProvider,
Ed Tanous904063f2017-03-02 16:48:24 -08002704 $HttpProvider,
2705 $HttpParamSerializerProvider,
2706 $HttpParamSerializerJQLikeProvider,
2707 $HttpBackendProvider,
2708 $xhrFactoryProvider,
2709 $jsonpCallbacksProvider,
2710 $LocationProvider,
2711 $LogProvider,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002712 $$MapProvider,
Ed Tanous904063f2017-03-02 16:48:24 -08002713 $ParseProvider,
2714 $RootScopeProvider,
2715 $QProvider,
2716 $$QProvider,
2717 $$SanitizeUriProvider,
2718 $SceProvider,
2719 $SceDelegateProvider,
2720 $SnifferProvider,
2721 $TemplateCacheProvider,
2722 $TemplateRequestProvider,
2723 $$TestabilityProvider,
2724 $TimeoutProvider,
2725 $$RAFProvider,
2726 $WindowProvider,
2727 $$jqLiteProvider,
2728 $$CookieReaderProvider
2729*/
2730
2731
2732/**
2733 * @ngdoc object
2734 * @name angular.version
2735 * @module ng
2736 * @description
2737 * An object that contains information about the current AngularJS version.
2738 *
2739 * This object has the following properties:
2740 *
2741 * - `full` – `{string}` – Full version string, such as "0.9.18".
2742 * - `major` – `{number}` – Major version number, such as "0".
2743 * - `minor` – `{number}` – Minor version number, such as "9".
2744 * - `dot` – `{number}` – Dot version number, such as "18".
2745 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2746 */
2747var version = {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002748 // These placeholder strings will be replaced by grunt's `build` task.
2749 // They need to be double- or single-quoted.
2750 full: '1.6.4',
2751 major: 1,
2752 minor: 6,
2753 dot: 4,
2754 codeName: 'phenomenal-footnote'
Ed Tanous904063f2017-03-02 16:48:24 -08002755};
2756
2757
2758function publishExternalAPI(angular) {
2759 extend(angular, {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002760 'errorHandlingConfig': errorHandlingConfig,
Ed Tanous904063f2017-03-02 16:48:24 -08002761 'bootstrap': bootstrap,
2762 'copy': copy,
2763 'extend': extend,
2764 'merge': merge,
2765 'equals': equals,
2766 'element': jqLite,
2767 'forEach': forEach,
2768 'injector': createInjector,
2769 'noop': noop,
2770 'bind': bind,
2771 'toJson': toJson,
2772 'fromJson': fromJson,
2773 'identity': identity,
2774 'isUndefined': isUndefined,
2775 'isDefined': isDefined,
2776 'isString': isString,
2777 'isFunction': isFunction,
2778 'isObject': isObject,
2779 'isNumber': isNumber,
2780 'isElement': isElement,
2781 'isArray': isArray,
2782 'version': version,
2783 'isDate': isDate,
2784 'lowercase': lowercase,
2785 'uppercase': uppercase,
2786 'callbacks': {$$counter: 0},
2787 'getTestability': getTestability,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002788 'reloadWithDebugInfo': reloadWithDebugInfo,
Ed Tanous904063f2017-03-02 16:48:24 -08002789 '$$minErr': minErr,
2790 '$$csp': csp,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002791 '$$encodeUriSegment': encodeUriSegment,
2792 '$$encodeUriQuery': encodeUriQuery,
2793 '$$stringify': stringify
Ed Tanous904063f2017-03-02 16:48:24 -08002794 });
2795
2796 angularModule = setupModuleLoader(window);
2797
2798 angularModule('ng', ['ngLocale'], ['$provide',
2799 function ngModule($provide) {
2800 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2801 $provide.provider({
2802 $$sanitizeUri: $$SanitizeUriProvider
2803 });
2804 $provide.provider('$compile', $CompileProvider).
2805 directive({
2806 a: htmlAnchorDirective,
2807 input: inputDirective,
2808 textarea: inputDirective,
2809 form: formDirective,
2810 script: scriptDirective,
2811 select: selectDirective,
Ed Tanous904063f2017-03-02 16:48:24 -08002812 option: optionDirective,
2813 ngBind: ngBindDirective,
2814 ngBindHtml: ngBindHtmlDirective,
2815 ngBindTemplate: ngBindTemplateDirective,
2816 ngClass: ngClassDirective,
2817 ngClassEven: ngClassEvenDirective,
2818 ngClassOdd: ngClassOddDirective,
2819 ngCloak: ngCloakDirective,
2820 ngController: ngControllerDirective,
2821 ngForm: ngFormDirective,
2822 ngHide: ngHideDirective,
2823 ngIf: ngIfDirective,
2824 ngInclude: ngIncludeDirective,
2825 ngInit: ngInitDirective,
2826 ngNonBindable: ngNonBindableDirective,
2827 ngPluralize: ngPluralizeDirective,
2828 ngRepeat: ngRepeatDirective,
2829 ngShow: ngShowDirective,
2830 ngStyle: ngStyleDirective,
2831 ngSwitch: ngSwitchDirective,
2832 ngSwitchWhen: ngSwitchWhenDirective,
2833 ngSwitchDefault: ngSwitchDefaultDirective,
2834 ngOptions: ngOptionsDirective,
2835 ngTransclude: ngTranscludeDirective,
2836 ngModel: ngModelDirective,
2837 ngList: ngListDirective,
2838 ngChange: ngChangeDirective,
2839 pattern: patternDirective,
2840 ngPattern: patternDirective,
2841 required: requiredDirective,
2842 ngRequired: requiredDirective,
2843 minlength: minlengthDirective,
2844 ngMinlength: minlengthDirective,
2845 maxlength: maxlengthDirective,
2846 ngMaxlength: maxlengthDirective,
2847 ngValue: ngValueDirective,
2848 ngModelOptions: ngModelOptionsDirective
2849 }).
2850 directive({
2851 ngInclude: ngIncludeFillContentDirective
2852 }).
2853 directive(ngAttributeAliasDirectives).
2854 directive(ngEventDirectives);
2855 $provide.provider({
2856 $anchorScroll: $AnchorScrollProvider,
2857 $animate: $AnimateProvider,
2858 $animateCss: $CoreAnimateCssProvider,
2859 $$animateJs: $$CoreAnimateJsProvider,
2860 $$animateQueue: $$CoreAnimateQueueProvider,
2861 $$AnimateRunner: $$AnimateRunnerFactoryProvider,
2862 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
2863 $browser: $BrowserProvider,
2864 $cacheFactory: $CacheFactoryProvider,
2865 $controller: $ControllerProvider,
2866 $document: $DocumentProvider,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002867 $$isDocumentHidden: $$IsDocumentHiddenProvider,
Ed Tanous904063f2017-03-02 16:48:24 -08002868 $exceptionHandler: $ExceptionHandlerProvider,
2869 $filter: $FilterProvider,
2870 $$forceReflow: $$ForceReflowProvider,
2871 $interpolate: $InterpolateProvider,
2872 $interval: $IntervalProvider,
2873 $http: $HttpProvider,
2874 $httpParamSerializer: $HttpParamSerializerProvider,
2875 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2876 $httpBackend: $HttpBackendProvider,
2877 $xhrFactory: $xhrFactoryProvider,
2878 $jsonpCallbacks: $jsonpCallbacksProvider,
2879 $location: $LocationProvider,
2880 $log: $LogProvider,
2881 $parse: $ParseProvider,
2882 $rootScope: $RootScopeProvider,
2883 $q: $QProvider,
2884 $$q: $$QProvider,
2885 $sce: $SceProvider,
2886 $sceDelegate: $SceDelegateProvider,
2887 $sniffer: $SnifferProvider,
2888 $templateCache: $TemplateCacheProvider,
2889 $templateRequest: $TemplateRequestProvider,
2890 $$testability: $$TestabilityProvider,
2891 $timeout: $TimeoutProvider,
2892 $window: $WindowProvider,
2893 $$rAF: $$RAFProvider,
2894 $$jqLite: $$jqLiteProvider,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002895 $$Map: $$MapProvider,
Ed Tanous904063f2017-03-02 16:48:24 -08002896 $$cookieReader: $$CookieReaderProvider
2897 });
2898 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07002899 ])
2900 .info({ angularVersion: '1.6.4' });
Ed Tanous904063f2017-03-02 16:48:24 -08002901}
2902
2903/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2904 * Any commits to this file should be reviewed with security in mind. *
2905 * Changes to this file can potentially create security vulnerabilities. *
2906 * An approval from 2 Core members with history of modifying *
2907 * this file is required. *
2908 * *
2909 * Does the change somehow allow for arbitrary javascript to be executed? *
2910 * Or allows for someone to change the prototype of built-in objects? *
2911 * Or gives undesired access to variables likes document or window? *
2912 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2913
Ed Tanous4758d5b2017-06-06 15:28:13 -07002914/* global
2915 JQLitePrototype: true,
Ed Tanous904063f2017-03-02 16:48:24 -08002916 BOOLEAN_ATTR: true,
Ed Tanous4758d5b2017-06-06 15:28:13 -07002917 ALIASED_ATTR: true
Ed Tanous904063f2017-03-02 16:48:24 -08002918*/
2919
2920//////////////////////////////////
2921//JQLite
2922//////////////////////////////////
2923
2924/**
2925 * @ngdoc function
2926 * @name angular.element
2927 * @module ng
2928 * @kind function
2929 *
2930 * @description
2931 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2932 *
2933 * If jQuery is available, `angular.element` is an alias for the
2934 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2935 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
2936 *
2937 * jqLite is a tiny, API-compatible subset of jQuery that allows
2938 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
2939 * commonly needed functionality with the goal of having a very small footprint.
2940 *
2941 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
2942 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
2943 * specific version of jQuery if multiple versions exist on the page.
2944 *
2945 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or
2946 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
2947 *
2948 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
2949 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
2950 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
2951 *
2952 * ## Angular's jqLite
2953 * jqLite provides only the following jQuery methods:
2954 *
2955 * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
2956 * - [`after()`](http://api.jquery.com/after/)
2957 * - [`append()`](http://api.jquery.com/append/)
2958 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
Ed Tanous4758d5b2017-06-06 15:28:13 -07002959 * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
Ed Tanous904063f2017-03-02 16:48:24 -08002960 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2961 * - [`clone()`](http://api.jquery.com/clone/)
2962 * - [`contents()`](http://api.jquery.com/contents/)
2963 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
2964 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
2965 * - [`data()`](http://api.jquery.com/data/)
2966 * - [`detach()`](http://api.jquery.com/detach/)
2967 * - [`empty()`](http://api.jquery.com/empty/)
2968 * - [`eq()`](http://api.jquery.com/eq/)
2969 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2970 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2971 * - [`html()`](http://api.jquery.com/html/)
2972 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2973 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2974 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2975 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2976 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2977 * - [`prepend()`](http://api.jquery.com/prepend/)
2978 * - [`prop()`](http://api.jquery.com/prop/)
Ed Tanous4758d5b2017-06-06 15:28:13 -07002979 * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`)
Ed Tanous904063f2017-03-02 16:48:24 -08002980 * - [`remove()`](http://api.jquery.com/remove/)
Ed Tanous4758d5b2017-06-06 15:28:13 -07002981 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes
Ed Tanous904063f2017-03-02 16:48:24 -08002982 * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
2983 * - [`removeData()`](http://api.jquery.com/removeData/)
2984 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2985 * - [`text()`](http://api.jquery.com/text/)
2986 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument
2987 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers
Ed Tanous4758d5b2017-06-06 15:28:13 -07002988 * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter
Ed Tanous904063f2017-03-02 16:48:24 -08002989 * - [`val()`](http://api.jquery.com/val/)
2990 * - [`wrap()`](http://api.jquery.com/wrap/)
2991 *
2992 * ## jQuery/jqLite Extras
2993 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2994 *
2995 * ### Events
2996 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2997 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2998 * element before it is removed.
2999 *
3000 * ### Methods
3001 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
3002 * retrieves controller associated with the `ngController` directive. If `name` is provided as
3003 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
3004 * `'ngModel'`).
3005 * - `injector()` - retrieves the injector of the current element or its parent.
3006 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
3007 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
3008 * be enabled.
3009 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
3010 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
3011 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
3012 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
3013 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
3014 * parent element is reached.
3015 *
3016 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
3017 * https://github.com/angular/angular.js/issues/14251 for more information.
3018 *
3019 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
3020 * @returns {Object} jQuery object.
3021 */
3022
3023JQLite.expando = 'ng339';
3024
3025var jqCache = JQLite.cache = {},
Ed Tanous4758d5b2017-06-06 15:28:13 -07003026 jqId = 1;
Ed Tanous904063f2017-03-02 16:48:24 -08003027
3028/*
3029 * !!! This is an undocumented "private" function !!!
3030 */
3031JQLite._data = function(node) {
3032 //jQuery always returns an object on cache miss
3033 return this.cache[node[this.expando]] || {};
3034};
3035
3036function jqNextId() { return ++jqId; }
3037
3038
Ed Tanous4758d5b2017-06-06 15:28:13 -07003039var DASH_LOWERCASE_REGEXP = /-([a-z])/g;
3040var MS_HACK_REGEXP = /^-ms-/;
3041var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' };
Ed Tanous904063f2017-03-02 16:48:24 -08003042var jqLiteMinErr = minErr('jqLite');
3043
3044/**
Ed Tanous4758d5b2017-06-06 15:28:13 -07003045 * Converts kebab-case to camelCase.
3046 * There is also a special case for the ms prefix starting with a lowercase letter.
Ed Tanous904063f2017-03-02 16:48:24 -08003047 * @param name Name to normalize
3048 */
Ed Tanous4758d5b2017-06-06 15:28:13 -07003049function cssKebabToCamel(name) {
3050 return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-'));
3051}
3052
3053function fnCamelCaseReplace(all, letter) {
3054 return letter.toUpperCase();
3055}
3056
3057/**
3058 * Converts kebab-case to camelCase.
3059 * @param name Name to normalize
3060 */
3061function kebabToCamel(name) {
3062 return name
3063 .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
Ed Tanous904063f2017-03-02 16:48:24 -08003064}
3065
3066var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
3067var HTML_REGEXP = /<|&#?\w+;/;
3068var TAG_NAME_REGEXP = /<([\w:-]+)/;
3069var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
3070
3071var wrapMap = {
3072 'option': [1, '<select multiple="multiple">', '</select>'],
3073
3074 'thead': [1, '<table>', '</table>'],
3075 'col': [2, '<table><colgroup>', '</colgroup></table>'],
3076 'tr': [2, '<table><tbody>', '</tbody></table>'],
3077 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
Ed Tanous4758d5b2017-06-06 15:28:13 -07003078 '_default': [0, '', '']
Ed Tanous904063f2017-03-02 16:48:24 -08003079};
3080
3081wrapMap.optgroup = wrapMap.option;
3082wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
3083wrapMap.th = wrapMap.td;
3084
3085
3086function jqLiteIsTextNode(html) {
3087 return !HTML_REGEXP.test(html);
3088}
3089
3090function jqLiteAcceptsData(node) {
3091 // The window object can accept data but has no nodeType
3092 // Otherwise we are only interested in elements (1) and documents (9)
3093 var nodeType = node.nodeType;
3094 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
3095}
3096
3097function jqLiteHasData(node) {
3098 for (var key in jqCache[node.ng339]) {
3099 return true;
3100 }
3101 return false;
3102}
3103
Ed Tanous904063f2017-03-02 16:48:24 -08003104function jqLiteBuildFragment(html, context) {
3105 var tmp, tag, wrap,
3106 fragment = context.createDocumentFragment(),
3107 nodes = [], i;
3108
3109 if (jqLiteIsTextNode(html)) {
3110 // Convert non-html into a text node
3111 nodes.push(context.createTextNode(html));
3112 } else {
3113 // Convert html into DOM nodes
Ed Tanous4758d5b2017-06-06 15:28:13 -07003114 tmp = fragment.appendChild(context.createElement('div'));
3115 tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase();
Ed Tanous904063f2017-03-02 16:48:24 -08003116 wrap = wrapMap[tag] || wrapMap._default;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003117 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1></$2>') + wrap[2];
Ed Tanous904063f2017-03-02 16:48:24 -08003118
3119 // Descend through wrappers to the right content
3120 i = wrap[0];
3121 while (i--) {
3122 tmp = tmp.lastChild;
3123 }
3124
3125 nodes = concat(nodes, tmp.childNodes);
3126
3127 tmp = fragment.firstChild;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003128 tmp.textContent = '';
Ed Tanous904063f2017-03-02 16:48:24 -08003129 }
3130
3131 // Remove wrapper from fragment
Ed Tanous4758d5b2017-06-06 15:28:13 -07003132 fragment.textContent = '';
3133 fragment.innerHTML = ''; // Clear inner HTML
Ed Tanous904063f2017-03-02 16:48:24 -08003134 forEach(nodes, function(node) {
3135 fragment.appendChild(node);
3136 });
3137
3138 return fragment;
3139}
3140
3141function jqLiteParseHTML(html, context) {
3142 context = context || window.document;
3143 var parsed;
3144
3145 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
3146 return [context.createElement(parsed[1])];
3147 }
3148
3149 if ((parsed = jqLiteBuildFragment(html, context))) {
3150 return parsed.childNodes;
3151 }
3152
3153 return [];
3154}
3155
3156function jqLiteWrapNode(node, wrapper) {
3157 var parent = node.parentNode;
3158
3159 if (parent) {
3160 parent.replaceChild(wrapper, node);
3161 }
3162
3163 wrapper.appendChild(node);
3164}
3165
3166
3167// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
Ed Tanous4758d5b2017-06-06 15:28:13 -07003168var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) {
3169 // eslint-disable-next-line no-bitwise
Ed Tanous904063f2017-03-02 16:48:24 -08003170 return !!(this.compareDocumentPosition(arg) & 16);
Ed Tanous904063f2017-03-02 16:48:24 -08003171};
3172
3173/////////////////////////////////////////////
3174function JQLite(element) {
3175 if (element instanceof JQLite) {
3176 return element;
3177 }
3178
3179 var argIsString;
3180
3181 if (isString(element)) {
3182 element = trim(element);
3183 argIsString = true;
3184 }
3185 if (!(this instanceof JQLite)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003186 if (argIsString && element.charAt(0) !== '<') {
Ed Tanous904063f2017-03-02 16:48:24 -08003187 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
3188 }
3189 return new JQLite(element);
3190 }
3191
3192 if (argIsString) {
3193 jqLiteAddNodes(this, jqLiteParseHTML(element));
Ed Tanous4758d5b2017-06-06 15:28:13 -07003194 } else if (isFunction(element)) {
3195 jqLiteReady(element);
Ed Tanous904063f2017-03-02 16:48:24 -08003196 } else {
3197 jqLiteAddNodes(this, element);
3198 }
3199}
3200
3201function jqLiteClone(element) {
3202 return element.cloneNode(true);
3203}
3204
3205function jqLiteDealoc(element, onlyDescendants) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003206 if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]);
Ed Tanous904063f2017-03-02 16:48:24 -08003207
3208 if (element.querySelectorAll) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003209 jqLite.cleanData(element.querySelectorAll('*'));
Ed Tanous904063f2017-03-02 16:48:24 -08003210 }
3211}
3212
3213function jqLiteOff(element, type, fn, unsupported) {
3214 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
3215
3216 var expandoStore = jqLiteExpandoStore(element);
3217 var events = expandoStore && expandoStore.events;
3218 var handle = expandoStore && expandoStore.handle;
3219
3220 if (!handle) return; //no listeners registered
3221
3222 if (!type) {
3223 for (type in events) {
3224 if (type !== '$destroy') {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003225 element.removeEventListener(type, handle);
Ed Tanous904063f2017-03-02 16:48:24 -08003226 }
3227 delete events[type];
3228 }
3229 } else {
3230
3231 var removeHandler = function(type) {
3232 var listenerFns = events[type];
3233 if (isDefined(fn)) {
3234 arrayRemove(listenerFns || [], fn);
3235 }
3236 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003237 element.removeEventListener(type, handle);
Ed Tanous904063f2017-03-02 16:48:24 -08003238 delete events[type];
3239 }
3240 };
3241
3242 forEach(type.split(' '), function(type) {
3243 removeHandler(type);
3244 if (MOUSE_EVENT_MAP[type]) {
3245 removeHandler(MOUSE_EVENT_MAP[type]);
3246 }
3247 });
3248 }
3249}
3250
3251function jqLiteRemoveData(element, name) {
3252 var expandoId = element.ng339;
3253 var expandoStore = expandoId && jqCache[expandoId];
3254
3255 if (expandoStore) {
3256 if (name) {
3257 delete expandoStore.data[name];
3258 return;
3259 }
3260
3261 if (expandoStore.handle) {
3262 if (expandoStore.events.$destroy) {
3263 expandoStore.handle({}, '$destroy');
3264 }
3265 jqLiteOff(element);
3266 }
3267 delete jqCache[expandoId];
3268 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
3269 }
3270}
3271
3272
3273function jqLiteExpandoStore(element, createIfNecessary) {
3274 var expandoId = element.ng339,
3275 expandoStore = expandoId && jqCache[expandoId];
3276
3277 if (createIfNecessary && !expandoStore) {
3278 element.ng339 = expandoId = jqNextId();
3279 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
3280 }
3281
3282 return expandoStore;
3283}
3284
3285
3286function jqLiteData(element, key, value) {
3287 if (jqLiteAcceptsData(element)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003288 var prop;
Ed Tanous904063f2017-03-02 16:48:24 -08003289
3290 var isSimpleSetter = isDefined(value);
3291 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
3292 var massGetter = !key;
3293 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
3294 var data = expandoStore && expandoStore.data;
3295
3296 if (isSimpleSetter) { // data('key', value)
Ed Tanous4758d5b2017-06-06 15:28:13 -07003297 data[kebabToCamel(key)] = value;
Ed Tanous904063f2017-03-02 16:48:24 -08003298 } else {
3299 if (massGetter) { // data()
3300 return data;
3301 } else {
3302 if (isSimpleGetter) { // data('key')
3303 // don't force creation of expandoStore if it doesn't exist yet
Ed Tanous4758d5b2017-06-06 15:28:13 -07003304 return data && data[kebabToCamel(key)];
Ed Tanous904063f2017-03-02 16:48:24 -08003305 } else { // mass-setter: data({key1: val1, key2: val2})
Ed Tanous4758d5b2017-06-06 15:28:13 -07003306 for (prop in key) {
3307 data[kebabToCamel(prop)] = key[prop];
3308 }
Ed Tanous904063f2017-03-02 16:48:24 -08003309 }
3310 }
3311 }
3312 }
3313}
3314
3315function jqLiteHasClass(element, selector) {
3316 if (!element.getAttribute) return false;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003317 return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
3318 indexOf(' ' + selector + ' ') > -1);
Ed Tanous904063f2017-03-02 16:48:24 -08003319}
3320
3321function jqLiteRemoveClass(element, cssClasses) {
3322 if (cssClasses && element.setAttribute) {
3323 forEach(cssClasses.split(' '), function(cssClass) {
3324 element.setAttribute('class', trim(
Ed Tanous4758d5b2017-06-06 15:28:13 -07003325 (' ' + (element.getAttribute('class') || '') + ' ')
3326 .replace(/[\n\t]/g, ' ')
3327 .replace(' ' + trim(cssClass) + ' ', ' '))
Ed Tanous904063f2017-03-02 16:48:24 -08003328 );
3329 });
3330 }
3331}
3332
3333function jqLiteAddClass(element, cssClasses) {
3334 if (cssClasses && element.setAttribute) {
3335 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
Ed Tanous4758d5b2017-06-06 15:28:13 -07003336 .replace(/[\n\t]/g, ' ');
Ed Tanous904063f2017-03-02 16:48:24 -08003337
3338 forEach(cssClasses.split(' '), function(cssClass) {
3339 cssClass = trim(cssClass);
3340 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3341 existingClasses += cssClass + ' ';
3342 }
3343 });
3344
3345 element.setAttribute('class', trim(existingClasses));
3346 }
3347}
3348
3349
3350function jqLiteAddNodes(root, elements) {
3351 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3352
3353 if (elements) {
3354
3355 // if a Node (the most common case)
3356 if (elements.nodeType) {
3357 root[root.length++] = elements;
3358 } else {
3359 var length = elements.length;
3360
3361 // if an Array or NodeList and not a Window
3362 if (typeof length === 'number' && elements.window !== elements) {
3363 if (length) {
3364 for (var i = 0; i < length; i++) {
3365 root[root.length++] = elements[i];
3366 }
3367 }
3368 } else {
3369 root[root.length++] = elements;
3370 }
3371 }
3372 }
3373}
3374
3375
3376function jqLiteController(element, name) {
3377 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3378}
3379
3380function jqLiteInheritedData(element, name, value) {
3381 // if element is the document object work with the html element instead
3382 // this makes $(document).scope() possible
Ed Tanous4758d5b2017-06-06 15:28:13 -07003383 if (element.nodeType === NODE_TYPE_DOCUMENT) {
Ed Tanous904063f2017-03-02 16:48:24 -08003384 element = element.documentElement;
3385 }
3386 var names = isArray(name) ? name : [name];
3387
3388 while (element) {
3389 for (var i = 0, ii = names.length; i < ii; i++) {
3390 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3391 }
3392
3393 // If dealing with a document fragment node with a host element, and no parent, use the host
3394 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3395 // to lookup parent controllers.
3396 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3397 }
3398}
3399
3400function jqLiteEmpty(element) {
3401 jqLiteDealoc(element, true);
3402 while (element.firstChild) {
3403 element.removeChild(element.firstChild);
3404 }
3405}
3406
3407function jqLiteRemove(element, keepData) {
3408 if (!keepData) jqLiteDealoc(element);
3409 var parent = element.parentNode;
3410 if (parent) parent.removeChild(element);
3411}
3412
3413
3414function jqLiteDocumentLoaded(action, win) {
3415 win = win || window;
3416 if (win.document.readyState === 'complete') {
3417 // Force the action to be run async for consistent behavior
3418 // from the action's point of view
3419 // i.e. it will definitely not be in a $apply
3420 win.setTimeout(action);
3421 } else {
3422 // No need to unbind this handler as load is only ever called once
3423 jqLite(win).on('load', action);
3424 }
3425}
3426
Ed Tanous4758d5b2017-06-06 15:28:13 -07003427function jqLiteReady(fn) {
3428 function trigger() {
3429 window.document.removeEventListener('DOMContentLoaded', trigger);
3430 window.removeEventListener('load', trigger);
3431 fn();
3432 }
3433
3434 // check if document is already loaded
3435 if (window.document.readyState === 'complete') {
3436 window.setTimeout(fn);
3437 } else {
3438 // We can not use jqLite since we are not done loading and jQuery could be loaded later.
3439
3440 // Works for modern browsers and IE9
3441 window.document.addEventListener('DOMContentLoaded', trigger);
3442
3443 // Fallback to window.onload for others
3444 window.addEventListener('load', trigger);
3445 }
3446}
3447
Ed Tanous904063f2017-03-02 16:48:24 -08003448//////////////////////////////////////////
3449// Functions which are declared directly.
3450//////////////////////////////////////////
3451var JQLitePrototype = JQLite.prototype = {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003452 ready: jqLiteReady,
Ed Tanous904063f2017-03-02 16:48:24 -08003453 toString: function() {
3454 var value = [];
3455 forEach(this, function(e) { value.push('' + e);});
3456 return '[' + value.join(', ') + ']';
3457 },
3458
3459 eq: function(index) {
3460 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3461 },
3462
3463 length: 0,
3464 push: push,
3465 sort: [].sort,
3466 splice: [].splice
3467};
3468
3469//////////////////////////////////////////
3470// Functions iterating getter/setters.
3471// these functions return self on setter and
3472// value on get.
3473//////////////////////////////////////////
3474var BOOLEAN_ATTR = {};
3475forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3476 BOOLEAN_ATTR[lowercase(value)] = value;
3477});
3478var BOOLEAN_ELEMENTS = {};
3479forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3480 BOOLEAN_ELEMENTS[value] = true;
3481});
3482var ALIASED_ATTR = {
3483 'ngMinlength': 'minlength',
3484 'ngMaxlength': 'maxlength',
3485 'ngMin': 'min',
3486 'ngMax': 'max',
Ed Tanous4758d5b2017-06-06 15:28:13 -07003487 'ngPattern': 'pattern',
3488 'ngStep': 'step'
Ed Tanous904063f2017-03-02 16:48:24 -08003489};
3490
3491function getBooleanAttrName(element, name) {
3492 // check dom last since we will most likely fail on name
3493 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3494
3495 // booleanAttr is here twice to minimize DOM access
3496 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3497}
3498
3499function getAliasedAttrName(name) {
3500 return ALIASED_ATTR[name];
3501}
3502
3503forEach({
3504 data: jqLiteData,
3505 removeData: jqLiteRemoveData,
3506 hasData: jqLiteHasData,
Ed Tanous4758d5b2017-06-06 15:28:13 -07003507 cleanData: function jqLiteCleanData(nodes) {
3508 for (var i = 0, ii = nodes.length; i < ii; i++) {
3509 jqLiteRemoveData(nodes[i]);
3510 }
3511 }
Ed Tanous904063f2017-03-02 16:48:24 -08003512}, function(fn, name) {
3513 JQLite[name] = fn;
3514});
3515
3516forEach({
3517 data: jqLiteData,
3518 inheritedData: jqLiteInheritedData,
3519
3520 scope: function(element) {
3521 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3522 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3523 },
3524
3525 isolateScope: function(element) {
3526 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3527 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3528 },
3529
3530 controller: jqLiteController,
3531
3532 injector: function(element) {
3533 return jqLiteInheritedData(element, '$injector');
3534 },
3535
3536 removeAttr: function(element, name) {
3537 element.removeAttribute(name);
3538 },
3539
3540 hasClass: jqLiteHasClass,
3541
3542 css: function(element, name, value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003543 name = cssKebabToCamel(name);
Ed Tanous904063f2017-03-02 16:48:24 -08003544
3545 if (isDefined(value)) {
3546 element.style[name] = value;
3547 } else {
3548 return element.style[name];
3549 }
3550 },
3551
3552 attr: function(element, name, value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003553 var ret;
Ed Tanous904063f2017-03-02 16:48:24 -08003554 var nodeType = element.nodeType;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003555 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT ||
3556 !element.getAttribute) {
Ed Tanous904063f2017-03-02 16:48:24 -08003557 return;
3558 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07003559
Ed Tanous904063f2017-03-02 16:48:24 -08003560 var lowercasedName = lowercase(name);
Ed Tanous4758d5b2017-06-06 15:28:13 -07003561 var isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
3562
3563 if (isDefined(value)) {
3564 // setter
3565
3566 if (value === null || (value === false && isBooleanAttr)) {
3567 element.removeAttribute(name);
Ed Tanous904063f2017-03-02 16:48:24 -08003568 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003569 element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
Ed Tanous904063f2017-03-02 16:48:24 -08003570 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07003571 } else {
3572 // getter
3573
3574 ret = element.getAttribute(name);
3575
3576 if (isBooleanAttr && ret !== null) {
3577 ret = lowercasedName;
3578 }
3579 // Normalize non-existing attributes to undefined (as jQuery).
Ed Tanous904063f2017-03-02 16:48:24 -08003580 return ret === null ? undefined : ret;
3581 }
3582 },
3583
3584 prop: function(element, name, value) {
3585 if (isDefined(value)) {
3586 element[name] = value;
3587 } else {
3588 return element[name];
3589 }
3590 },
3591
3592 text: (function() {
3593 getText.$dv = '';
3594 return getText;
3595
3596 function getText(element, value) {
3597 if (isUndefined(value)) {
3598 var nodeType = element.nodeType;
3599 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3600 }
3601 element.textContent = value;
3602 }
3603 })(),
3604
3605 val: function(element, value) {
3606 if (isUndefined(value)) {
3607 if (element.multiple && nodeName_(element) === 'select') {
3608 var result = [];
3609 forEach(element.options, function(option) {
3610 if (option.selected) {
3611 result.push(option.value || option.text);
3612 }
3613 });
Ed Tanous4758d5b2017-06-06 15:28:13 -07003614 return result;
Ed Tanous904063f2017-03-02 16:48:24 -08003615 }
3616 return element.value;
3617 }
3618 element.value = value;
3619 },
3620
3621 html: function(element, value) {
3622 if (isUndefined(value)) {
3623 return element.innerHTML;
3624 }
3625 jqLiteDealoc(element, true);
3626 element.innerHTML = value;
3627 },
3628
3629 empty: jqLiteEmpty
3630}, function(fn, name) {
3631 /**
3632 * Properties: writes return selection, reads return first value
3633 */
3634 JQLite.prototype[name] = function(arg1, arg2) {
3635 var i, key;
3636 var nodeCount = this.length;
3637
3638 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3639 // in a way that survives minification.
3640 // jqLiteEmpty takes no arguments but is a setter.
3641 if (fn !== jqLiteEmpty &&
Ed Tanous4758d5b2017-06-06 15:28:13 -07003642 (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
Ed Tanous904063f2017-03-02 16:48:24 -08003643 if (isObject(arg1)) {
3644
3645 // we are a write, but the object properties are the key/values
3646 for (i = 0; i < nodeCount; i++) {
3647 if (fn === jqLiteData) {
3648 // data() takes the whole object in jQuery
3649 fn(this[i], arg1);
3650 } else {
3651 for (key in arg1) {
3652 fn(this[i], key, arg1[key]);
3653 }
3654 }
3655 }
3656 // return self for chaining
3657 return this;
3658 } else {
3659 // we are a read, so read the first child.
3660 // TODO: do we still need this?
3661 var value = fn.$dv;
3662 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3663 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3664 for (var j = 0; j < jj; j++) {
3665 var nodeValue = fn(this[j], arg1, arg2);
3666 value = value ? value + nodeValue : nodeValue;
3667 }
3668 return value;
3669 }
3670 } else {
3671 // we are a write, so apply to all children
3672 for (i = 0; i < nodeCount; i++) {
3673 fn(this[i], arg1, arg2);
3674 }
3675 // return self for chaining
3676 return this;
3677 }
3678 };
3679});
3680
3681function createEventHandler(element, events) {
3682 var eventHandler = function(event, type) {
3683 // jQuery specific api
3684 event.isDefaultPrevented = function() {
3685 return event.defaultPrevented;
3686 };
3687
3688 var eventFns = events[type || event.type];
3689 var eventFnsLength = eventFns ? eventFns.length : 0;
3690
3691 if (!eventFnsLength) return;
3692
3693 if (isUndefined(event.immediatePropagationStopped)) {
3694 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3695 event.stopImmediatePropagation = function() {
3696 event.immediatePropagationStopped = true;
3697
3698 if (event.stopPropagation) {
3699 event.stopPropagation();
3700 }
3701
3702 if (originalStopImmediatePropagation) {
3703 originalStopImmediatePropagation.call(event);
3704 }
3705 };
3706 }
3707
3708 event.isImmediatePropagationStopped = function() {
3709 return event.immediatePropagationStopped === true;
3710 };
3711
3712 // Some events have special handlers that wrap the real handler
3713 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3714
3715 // Copy event handlers in case event handlers array is modified during execution.
3716 if ((eventFnsLength > 1)) {
3717 eventFns = shallowCopy(eventFns);
3718 }
3719
3720 for (var i = 0; i < eventFnsLength; i++) {
3721 if (!event.isImmediatePropagationStopped()) {
3722 handlerWrapper(element, event, eventFns[i]);
3723 }
3724 }
3725 };
3726
3727 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3728 // events on `element`
3729 eventHandler.elem = element;
3730 return eventHandler;
3731}
3732
3733function defaultHandlerWrapper(element, event, handler) {
3734 handler.call(element, event);
3735}
3736
3737function specialMouseHandlerWrapper(target, event, handler) {
3738 // Refer to jQuery's implementation of mouseenter & mouseleave
3739 // Read about mouseenter and mouseleave:
3740 // http://www.quirksmode.org/js/events_mouse.html#link8
3741 var related = event.relatedTarget;
3742 // For mousenter/leave call the handler if related is outside the target.
3743 // NB: No relatedTarget if the mouse left/entered the browser window
3744 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3745 handler.call(target, event);
3746 }
3747}
3748
3749//////////////////////////////////////////
3750// Functions iterating traversal.
3751// These functions chain results into a single
3752// selector.
3753//////////////////////////////////////////
3754forEach({
3755 removeData: jqLiteRemoveData,
3756
3757 on: function jqLiteOn(element, type, fn, unsupported) {
3758 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3759
3760 // Do not add event handlers to non-elements because they will not be cleaned up.
3761 if (!jqLiteAcceptsData(element)) {
3762 return;
3763 }
3764
3765 var expandoStore = jqLiteExpandoStore(element, true);
3766 var events = expandoStore.events;
3767 var handle = expandoStore.handle;
3768
3769 if (!handle) {
3770 handle = expandoStore.handle = createEventHandler(element, events);
3771 }
3772
3773 // http://jsperf.com/string-indexof-vs-split
3774 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3775 var i = types.length;
3776
3777 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3778 var eventFns = events[type];
3779
3780 if (!eventFns) {
3781 eventFns = events[type] = [];
3782 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3783 if (type !== '$destroy' && !noEventListener) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003784 element.addEventListener(type, handle);
Ed Tanous904063f2017-03-02 16:48:24 -08003785 }
3786 }
3787
3788 eventFns.push(fn);
3789 };
3790
3791 while (i--) {
3792 type = types[i];
3793 if (MOUSE_EVENT_MAP[type]) {
3794 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3795 addHandler(type, undefined, true);
3796 } else {
3797 addHandler(type);
3798 }
3799 }
3800 },
3801
3802 off: jqLiteOff,
3803
3804 one: function(element, type, fn) {
3805 element = jqLite(element);
3806
3807 //add the listener twice so that when it is called
3808 //you can remove the original function and still be
3809 //able to call element.off(ev, fn) normally
3810 element.on(type, function onFn() {
3811 element.off(type, fn);
3812 element.off(type, onFn);
3813 });
3814 element.on(type, fn);
3815 },
3816
3817 replaceWith: function(element, replaceNode) {
3818 var index, parent = element.parentNode;
3819 jqLiteDealoc(element);
3820 forEach(new JQLite(replaceNode), function(node) {
3821 if (index) {
3822 parent.insertBefore(node, index.nextSibling);
3823 } else {
3824 parent.replaceChild(node, element);
3825 }
3826 index = node;
3827 });
3828 },
3829
3830 children: function(element) {
3831 var children = [];
3832 forEach(element.childNodes, function(element) {
3833 if (element.nodeType === NODE_TYPE_ELEMENT) {
3834 children.push(element);
3835 }
3836 });
3837 return children;
3838 },
3839
3840 contents: function(element) {
3841 return element.contentDocument || element.childNodes || [];
3842 },
3843
3844 append: function(element, node) {
3845 var nodeType = element.nodeType;
3846 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3847
3848 node = new JQLite(node);
3849
3850 for (var i = 0, ii = node.length; i < ii; i++) {
3851 var child = node[i];
3852 element.appendChild(child);
3853 }
3854 },
3855
3856 prepend: function(element, node) {
3857 if (element.nodeType === NODE_TYPE_ELEMENT) {
3858 var index = element.firstChild;
3859 forEach(new JQLite(node), function(child) {
3860 element.insertBefore(child, index);
3861 });
3862 }
3863 },
3864
3865 wrap: function(element, wrapNode) {
3866 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
3867 },
3868
3869 remove: jqLiteRemove,
3870
3871 detach: function(element) {
3872 jqLiteRemove(element, true);
3873 },
3874
3875 after: function(element, newElement) {
3876 var index = element, parent = element.parentNode;
Ed Tanous904063f2017-03-02 16:48:24 -08003877
Ed Tanous4758d5b2017-06-06 15:28:13 -07003878 if (parent) {
3879 newElement = new JQLite(newElement);
3880
3881 for (var i = 0, ii = newElement.length; i < ii; i++) {
3882 var node = newElement[i];
3883 parent.insertBefore(node, index.nextSibling);
3884 index = node;
3885 }
Ed Tanous904063f2017-03-02 16:48:24 -08003886 }
3887 },
3888
3889 addClass: jqLiteAddClass,
3890 removeClass: jqLiteRemoveClass,
3891
3892 toggleClass: function(element, selector, condition) {
3893 if (selector) {
3894 forEach(selector.split(' '), function(className) {
3895 var classCondition = condition;
3896 if (isUndefined(classCondition)) {
3897 classCondition = !jqLiteHasClass(element, className);
3898 }
3899 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3900 });
3901 }
3902 },
3903
3904 parent: function(element) {
3905 var parent = element.parentNode;
3906 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3907 },
3908
3909 next: function(element) {
3910 return element.nextElementSibling;
3911 },
3912
3913 find: function(element, selector) {
3914 if (element.getElementsByTagName) {
3915 return element.getElementsByTagName(selector);
3916 } else {
3917 return [];
3918 }
3919 },
3920
3921 clone: jqLiteClone,
3922
3923 triggerHandler: function(element, event, extraParameters) {
3924
3925 var dummyEvent, eventFnsCopy, handlerArgs;
3926 var eventName = event.type || event;
3927 var expandoStore = jqLiteExpandoStore(element);
3928 var events = expandoStore && expandoStore.events;
3929 var eventFns = events && events[eventName];
3930
3931 if (eventFns) {
3932 // Create a dummy event to pass to the handlers
3933 dummyEvent = {
3934 preventDefault: function() { this.defaultPrevented = true; },
3935 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3936 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3937 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3938 stopPropagation: noop,
3939 type: eventName,
3940 target: element
3941 };
3942
3943 // If a custom event was provided then extend our dummy event with it
3944 if (event.type) {
3945 dummyEvent = extend(dummyEvent, event);
3946 }
3947
3948 // Copy event handlers in case event handlers array is modified during execution.
3949 eventFnsCopy = shallowCopy(eventFns);
3950 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3951
3952 forEach(eventFnsCopy, function(fn) {
3953 if (!dummyEvent.isImmediatePropagationStopped()) {
3954 fn.apply(element, handlerArgs);
3955 }
3956 });
3957 }
3958 }
3959}, function(fn, name) {
3960 /**
3961 * chaining functions
3962 */
3963 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3964 var value;
3965
3966 for (var i = 0, ii = this.length; i < ii; i++) {
3967 if (isUndefined(value)) {
3968 value = fn(this[i], arg1, arg2, arg3);
3969 if (isDefined(value)) {
3970 // any function which returns a value needs to be wrapped
3971 value = jqLite(value);
3972 }
3973 } else {
3974 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3975 }
3976 }
3977 return isDefined(value) ? value : this;
3978 };
Ed Tanous904063f2017-03-02 16:48:24 -08003979});
3980
Ed Tanous4758d5b2017-06-06 15:28:13 -07003981// bind legacy bind/unbind to on/off
3982JQLite.prototype.bind = JQLite.prototype.on;
3983JQLite.prototype.unbind = JQLite.prototype.off;
3984
Ed Tanous904063f2017-03-02 16:48:24 -08003985
3986// Provider for private $$jqLite service
Ed Tanous4758d5b2017-06-06 15:28:13 -07003987/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -08003988function $$jqLiteProvider() {
3989 this.$get = function $$jqLite() {
3990 return extend(JQLite, {
3991 hasClass: function(node, classes) {
3992 if (node.attr) node = node[0];
3993 return jqLiteHasClass(node, classes);
3994 },
3995 addClass: function(node, classes) {
3996 if (node.attr) node = node[0];
3997 return jqLiteAddClass(node, classes);
3998 },
3999 removeClass: function(node, classes) {
4000 if (node.attr) node = node[0];
4001 return jqLiteRemoveClass(node, classes);
4002 }
4003 });
4004 };
4005}
4006
4007/**
4008 * Computes a hash of an 'obj'.
4009 * Hash of a:
4010 * string is string
4011 * number is number as string
4012 * object is either result of calling $$hashKey function on the object or uniquely generated id,
4013 * that is also assigned to the $$hashKey property of the object.
4014 *
4015 * @param obj
4016 * @returns {string} hash string such that the same input will have the same hash string.
4017 * The resulting string key is in 'type:hashKey' format.
4018 */
4019function hashKey(obj, nextUidFn) {
4020 var key = obj && obj.$$hashKey;
4021
4022 if (key) {
4023 if (typeof key === 'function') {
4024 key = obj.$$hashKey();
4025 }
4026 return key;
4027 }
4028
4029 var objType = typeof obj;
Ed Tanous4758d5b2017-06-06 15:28:13 -07004030 if (objType === 'function' || (objType === 'object' && obj !== null)) {
Ed Tanous904063f2017-03-02 16:48:24 -08004031 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
4032 } else {
4033 key = objType + ':' + obj;
4034 }
4035
4036 return key;
4037}
4038
Ed Tanous4758d5b2017-06-06 15:28:13 -07004039// A minimal ES2015 Map implementation.
4040// Should be bug/feature equivalent to the native implementations of supported browsers
4041// (for the features required in Angular).
4042// See https://kangax.github.io/compat-table/es6/#test-Map
4043var nanKey = Object.create(null);
4044function NgMapShim() {
4045 this._keys = [];
4046 this._values = [];
4047 this._lastKey = NaN;
4048 this._lastIndex = -1;
Ed Tanous904063f2017-03-02 16:48:24 -08004049}
Ed Tanous4758d5b2017-06-06 15:28:13 -07004050NgMapShim.prototype = {
4051 _idx: function(key) {
4052 if (key === this._lastKey) {
4053 return this._lastIndex;
4054 }
4055 this._lastKey = key;
4056 this._lastIndex = this._keys.indexOf(key);
4057 return this._lastIndex;
Ed Tanous904063f2017-03-02 16:48:24 -08004058 },
Ed Tanous4758d5b2017-06-06 15:28:13 -07004059 _transformKey: function(key) {
4060 return isNumberNaN(key) ? nanKey : key;
4061 },
Ed Tanous904063f2017-03-02 16:48:24 -08004062 get: function(key) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004063 key = this._transformKey(key);
4064 var idx = this._idx(key);
4065 if (idx !== -1) {
4066 return this._values[idx];
4067 }
Ed Tanous904063f2017-03-02 16:48:24 -08004068 },
Ed Tanous4758d5b2017-06-06 15:28:13 -07004069 set: function(key, value) {
4070 key = this._transformKey(key);
4071 var idx = this._idx(key);
4072 if (idx === -1) {
4073 idx = this._lastIndex = this._keys.length;
4074 }
4075 this._keys[idx] = key;
4076 this._values[idx] = value;
Ed Tanous904063f2017-03-02 16:48:24 -08004077
Ed Tanous4758d5b2017-06-06 15:28:13 -07004078 // Support: IE11
4079 // Do not `return this` to simulate the partial IE11 implementation
4080 },
4081 delete: function(key) {
4082 key = this._transformKey(key);
4083 var idx = this._idx(key);
4084 if (idx === -1) {
4085 return false;
4086 }
4087 this._keys.splice(idx, 1);
4088 this._values.splice(idx, 1);
4089 this._lastKey = NaN;
4090 this._lastIndex = -1;
4091 return true;
Ed Tanous904063f2017-03-02 16:48:24 -08004092 }
4093};
4094
Ed Tanous4758d5b2017-06-06 15:28:13 -07004095// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
4096// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
4097// implementations get more stable, we can reconsider switching to `window.Map` (when available).
4098var NgMap = NgMapShim;
4099
4100var $$MapProvider = [/** @this */function() {
Ed Tanous904063f2017-03-02 16:48:24 -08004101 this.$get = [function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004102 return NgMap;
Ed Tanous904063f2017-03-02 16:48:24 -08004103 }];
4104}];
4105
4106/**
4107 * @ngdoc function
4108 * @module ng
4109 * @name angular.injector
4110 * @kind function
4111 *
4112 * @description
4113 * Creates an injector object that can be used for retrieving services as well as for
4114 * dependency injection (see {@link guide/di dependency injection}).
4115 *
4116 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
4117 * {@link angular.module}. The `ng` module must be explicitly added.
4118 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
4119 * disallows argument name annotation inference.
4120 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
4121 *
4122 * @example
4123 * Typical usage
4124 * ```js
4125 * // create an injector
4126 * var $injector = angular.injector(['ng']);
4127 *
4128 * // use the injector to kick off your application
4129 * // use the type inference to auto inject arguments, or use implicit injection
4130 * $injector.invoke(function($rootScope, $compile, $document) {
4131 * $compile($document)($rootScope);
4132 * $rootScope.$digest();
4133 * });
4134 * ```
4135 *
4136 * Sometimes you want to get access to the injector of a currently running Angular app
4137 * from outside Angular. Perhaps, you want to inject and compile some markup after the
4138 * application has been bootstrapped. You can do this using the extra `injector()` added
4139 * to JQuery/jqLite elements. See {@link angular.element}.
4140 *
4141 * *This is fairly rare but could be the case if a third party library is injecting the
4142 * markup.*
4143 *
4144 * In the following example a new block of HTML containing a `ng-controller`
4145 * directive is added to the end of the document body by JQuery. We then compile and link
4146 * it into the current AngularJS scope.
4147 *
4148 * ```js
4149 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
4150 * $(document.body).append($div);
4151 *
4152 * angular.element(document).injector().invoke(function($compile) {
4153 * var scope = angular.element($div).scope();
4154 * $compile($div)(scope);
4155 * });
4156 * ```
4157 */
4158
4159
4160/**
4161 * @ngdoc module
4162 * @name auto
4163 * @installation
4164 * @description
4165 *
4166 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
4167 */
4168
Ed Tanous4758d5b2017-06-06 15:28:13 -07004169var ARROW_ARG = /^([^(]+?)=>/;
4170var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m;
Ed Tanous904063f2017-03-02 16:48:24 -08004171var FN_ARG_SPLIT = /,/;
4172var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
4173var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
4174var $injectorMinErr = minErr('$injector');
4175
4176function stringifyFn(fn) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004177 return Function.prototype.toString.call(fn);
Ed Tanous904063f2017-03-02 16:48:24 -08004178}
4179
4180function extractArgs(fn) {
4181 var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
4182 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
4183 return args;
4184}
4185
4186function anonFn(fn) {
4187 // For anonymous functions, showing at the very least the function signature can help in
4188 // debugging.
4189 var args = extractArgs(fn);
4190 if (args) {
4191 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
4192 }
4193 return 'fn';
4194}
4195
4196function annotate(fn, strictDi, name) {
4197 var $inject,
4198 argDecl,
4199 last;
4200
4201 if (typeof fn === 'function') {
4202 if (!($inject = fn.$inject)) {
4203 $inject = [];
4204 if (fn.length) {
4205 if (strictDi) {
4206 if (!isString(name) || !name) {
4207 name = fn.name || anonFn(fn);
4208 }
4209 throw $injectorMinErr('strictdi',
4210 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
4211 }
4212 argDecl = extractArgs(fn);
4213 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
4214 arg.replace(FN_ARG, function(all, underscore, name) {
4215 $inject.push(name);
4216 });
4217 });
4218 }
4219 fn.$inject = $inject;
4220 }
4221 } else if (isArray(fn)) {
4222 last = fn.length - 1;
4223 assertArgFn(fn[last], 'fn');
4224 $inject = fn.slice(0, last);
4225 } else {
4226 assertArgFn(fn, 'fn', true);
4227 }
4228 return $inject;
4229}
4230
4231///////////////////////////////////////
4232
4233/**
4234 * @ngdoc service
4235 * @name $injector
4236 *
4237 * @description
4238 *
4239 * `$injector` is used to retrieve object instances as defined by
4240 * {@link auto.$provide provider}, instantiate types, invoke methods,
4241 * and load modules.
4242 *
4243 * The following always holds true:
4244 *
4245 * ```js
4246 * var $injector = angular.injector();
4247 * expect($injector.get('$injector')).toBe($injector);
4248 * expect($injector.invoke(function($injector) {
4249 * return $injector;
4250 * })).toBe($injector);
4251 * ```
4252 *
4253 * # Injection Function Annotation
4254 *
4255 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
4256 * following are all valid ways of annotating function with injection arguments and are equivalent.
4257 *
4258 * ```js
4259 * // inferred (only works if code not minified/obfuscated)
4260 * $injector.invoke(function(serviceA){});
4261 *
4262 * // annotated
4263 * function explicit(serviceA) {};
4264 * explicit.$inject = ['serviceA'];
4265 * $injector.invoke(explicit);
4266 *
4267 * // inline
4268 * $injector.invoke(['serviceA', function(serviceA){}]);
4269 * ```
4270 *
4271 * ## Inference
4272 *
4273 * In JavaScript calling `toString()` on a function returns the function definition. The definition
4274 * can then be parsed and the function arguments can be extracted. This method of discovering
4275 * annotations is disallowed when the injector is in strict mode.
4276 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
4277 * argument names.
4278 *
4279 * ## `$inject` Annotation
4280 * By adding an `$inject` property onto a function the injection parameters can be specified.
4281 *
4282 * ## Inline
4283 * As an array of injection names, where the last item in the array is the function to call.
4284 */
4285
4286/**
Ed Tanous4758d5b2017-06-06 15:28:13 -07004287 * @ngdoc property
4288 * @name $injector#modules
4289 * @type {Object}
4290 * @description
4291 * A hash containing all the modules that have been loaded into the
4292 * $injector.
4293 *
4294 * You can use this property to find out information about a module via the
4295 * {@link angular.Module#info `myModule.info(...)`} method.
4296 *
4297 * For example:
4298 *
4299 * ```
4300 * var info = $injector.modules['ngAnimate'].info();
4301 * ```
4302 *
4303 * **Do not use this property to attempt to modify the modules after the application
4304 * has been bootstrapped.**
4305 */
4306
4307
4308/**
Ed Tanous904063f2017-03-02 16:48:24 -08004309 * @ngdoc method
4310 * @name $injector#get
4311 *
4312 * @description
4313 * Return an instance of the service.
4314 *
4315 * @param {string} name The name of the instance to retrieve.
4316 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
4317 * @return {*} The instance.
4318 */
4319
4320/**
4321 * @ngdoc method
4322 * @name $injector#invoke
4323 *
4324 * @description
4325 * Invoke the method and supply the method arguments from the `$injector`.
4326 *
4327 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
4328 * injected according to the {@link guide/di $inject Annotation} rules.
4329 * @param {Object=} self The `this` for the invoked method.
4330 * @param {Object=} locals Optional object. If preset then any argument names are read from this
4331 * object first, before the `$injector` is consulted.
4332 * @returns {*} the value returned by the invoked `fn` function.
4333 */
4334
4335/**
4336 * @ngdoc method
4337 * @name $injector#has
4338 *
4339 * @description
4340 * Allows the user to query if the particular service exists.
4341 *
4342 * @param {string} name Name of the service to query.
4343 * @returns {boolean} `true` if injector has given service.
4344 */
4345
4346/**
4347 * @ngdoc method
4348 * @name $injector#instantiate
4349 * @description
4350 * Create a new instance of JS type. The method takes a constructor function, invokes the new
4351 * operator, and supplies all of the arguments to the constructor function as specified by the
4352 * constructor annotation.
4353 *
4354 * @param {Function} Type Annotated constructor function.
4355 * @param {Object=} locals Optional object. If preset then any argument names are read from this
4356 * object first, before the `$injector` is consulted.
4357 * @returns {Object} new instance of `Type`.
4358 */
4359
4360/**
4361 * @ngdoc method
4362 * @name $injector#annotate
4363 *
4364 * @description
4365 * Returns an array of service names which the function is requesting for injection. This API is
4366 * used by the injector to determine which services need to be injected into the function when the
4367 * function is invoked. There are three ways in which the function can be annotated with the needed
4368 * dependencies.
4369 *
4370 * # Argument names
4371 *
4372 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4373 * by converting the function into a string using `toString()` method and extracting the argument
4374 * names.
4375 * ```js
4376 * // Given
4377 * function MyController($scope, $route) {
4378 * // ...
4379 * }
4380 *
4381 * // Then
4382 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4383 * ```
4384 *
4385 * You can disallow this method by using strict injection mode.
4386 *
4387 * This method does not work with code minification / obfuscation. For this reason the following
4388 * annotation strategies are supported.
4389 *
4390 * # The `$inject` property
4391 *
4392 * If a function has an `$inject` property and its value is an array of strings, then the strings
4393 * represent names of services to be injected into the function.
4394 * ```js
4395 * // Given
4396 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4397 * // ...
4398 * }
4399 * // Define function dependencies
4400 * MyController['$inject'] = ['$scope', '$route'];
4401 *
4402 * // Then
4403 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4404 * ```
4405 *
4406 * # The array notation
4407 *
4408 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4409 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4410 * a way that survives minification is a better choice:
4411 *
4412 * ```js
4413 * // We wish to write this (not minification / obfuscation safe)
4414 * injector.invoke(function($compile, $rootScope) {
4415 * // ...
4416 * });
4417 *
4418 * // We are forced to write break inlining
4419 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4420 * // ...
4421 * };
4422 * tmpFn.$inject = ['$compile', '$rootScope'];
4423 * injector.invoke(tmpFn);
4424 *
4425 * // To better support inline function the inline annotation is supported
4426 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4427 * // ...
4428 * }]);
4429 *
4430 * // Therefore
4431 * expect(injector.annotate(
4432 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4433 * ).toEqual(['$compile', '$rootScope']);
4434 * ```
4435 *
4436 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4437 * be retrieved as described above.
4438 *
4439 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4440 *
4441 * @returns {Array.<string>} The names of the services which the function requires.
4442 */
4443
4444
4445
Ed Tanous904063f2017-03-02 16:48:24 -08004446/**
4447 * @ngdoc service
4448 * @name $provide
4449 *
4450 * @description
4451 *
4452 * The {@link auto.$provide $provide} service has a number of methods for registering components
4453 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4454 * {@link angular.Module}.
4455 *
4456 * An Angular **service** is a singleton object created by a **service factory**. These **service
4457 * factories** are functions which, in turn, are created by a **service provider**.
4458 * The **service providers** are constructor functions. When instantiated they must contain a
4459 * property called `$get`, which holds the **service factory** function.
4460 *
4461 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4462 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4463 * function to get the instance of the **service**.
4464 *
4465 * Often services have no configuration options and there is no need to add methods to the service
4466 * provider. The provider will be no more than a constructor function with a `$get` property. For
4467 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4468 * services without specifying a provider.
4469 *
4470 * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the
4471 * {@link auto.$injector $injector}
4472 * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by
4473 * providers and services.
4474 * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by
4475 * services, not providers.
4476 * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function**
4477 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4478 * given factory function.
4479 * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function**
4480 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4481 * a new object using the given constructor function.
4482 * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that
4483 * will be able to modify or replace the implementation of another service.
4484 *
4485 * See the individual methods for more information and examples.
4486 */
4487
4488/**
4489 * @ngdoc method
4490 * @name $provide#provider
4491 * @description
4492 *
4493 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4494 * are constructor functions, whose instances are responsible for "providing" a factory for a
4495 * service.
4496 *
4497 * Service provider names start with the name of the service they provide followed by `Provider`.
4498 * For example, the {@link ng.$log $log} service has a provider called
4499 * {@link ng.$logProvider $logProvider}.
4500 *
4501 * Service provider objects can have additional methods which allow configuration of the provider
4502 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4503 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4504 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4505 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4506 * console or not.
4507 *
4508 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4509 'Provider'` key.
4510 * @param {(Object|function())} provider If the provider is:
4511 *
4512 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4513 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4514 * - `Constructor`: a new instance of the provider will be created using
4515 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4516 *
4517 * @returns {Object} registered provider instance
4518
4519 * @example
4520 *
4521 * The following example shows how to create a simple event tracking service and register it using
4522 * {@link auto.$provide#provider $provide.provider()}.
4523 *
4524 * ```js
4525 * // Define the eventTracker provider
4526 * function EventTrackerProvider() {
4527 * var trackingUrl = '/track';
4528 *
4529 * // A provider method for configuring where the tracked events should been saved
4530 * this.setTrackingUrl = function(url) {
4531 * trackingUrl = url;
4532 * };
4533 *
4534 * // The service factory function
4535 * this.$get = ['$http', function($http) {
4536 * var trackedEvents = {};
4537 * return {
4538 * // Call this to track an event
4539 * event: function(event) {
4540 * var count = trackedEvents[event] || 0;
4541 * count += 1;
4542 * trackedEvents[event] = count;
4543 * return count;
4544 * },
4545 * // Call this to save the tracked events to the trackingUrl
4546 * save: function() {
4547 * $http.post(trackingUrl, trackedEvents);
4548 * }
4549 * };
4550 * }];
4551 * }
4552 *
4553 * describe('eventTracker', function() {
4554 * var postSpy;
4555 *
4556 * beforeEach(module(function($provide) {
4557 * // Register the eventTracker provider
4558 * $provide.provider('eventTracker', EventTrackerProvider);
4559 * }));
4560 *
4561 * beforeEach(module(function(eventTrackerProvider) {
4562 * // Configure eventTracker provider
4563 * eventTrackerProvider.setTrackingUrl('/custom-track');
4564 * }));
4565 *
4566 * it('tracks events', inject(function(eventTracker) {
4567 * expect(eventTracker.event('login')).toEqual(1);
4568 * expect(eventTracker.event('login')).toEqual(2);
4569 * }));
4570 *
4571 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4572 * postSpy = spyOn($http, 'post');
4573 * eventTracker.event('login');
4574 * eventTracker.save();
4575 * expect(postSpy).toHaveBeenCalled();
4576 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4577 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4578 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4579 * }));
4580 * });
4581 * ```
4582 */
4583
4584/**
4585 * @ngdoc method
4586 * @name $provide#factory
4587 * @description
4588 *
4589 * Register a **service factory**, which will be called to return the service instance.
4590 * This is short for registering a service where its provider consists of only a `$get` property,
4591 * which is the given service factory function.
4592 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4593 * configure your service in a provider.
4594 *
4595 * @param {string} name The name of the instance.
4596 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4597 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4598 * @returns {Object} registered provider instance
4599 *
4600 * @example
4601 * Here is an example of registering a service
4602 * ```js
4603 * $provide.factory('ping', ['$http', function($http) {
4604 * return function ping() {
4605 * return $http.send('/ping');
4606 * };
4607 * }]);
4608 * ```
4609 * You would then inject and use this service like this:
4610 * ```js
4611 * someModule.controller('Ctrl', ['ping', function(ping) {
4612 * ping();
4613 * }]);
4614 * ```
4615 */
4616
4617
4618/**
4619 * @ngdoc method
4620 * @name $provide#service
4621 * @description
4622 *
4623 * Register a **service constructor**, which will be invoked with `new` to create the service
4624 * instance.
4625 * This is short for registering a service where its provider's `$get` property is a factory
4626 * function that returns an instance instantiated by the injector from the service constructor
4627 * function.
4628 *
4629 * Internally it looks a bit like this:
4630 *
4631 * ```
4632 * {
4633 * $get: function() {
4634 * return $injector.instantiate(constructor);
4635 * }
4636 * }
4637 * ```
4638 *
4639 *
4640 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4641 * as a type/class.
4642 *
4643 * @param {string} name The name of the instance.
4644 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4645 * that will be instantiated.
4646 * @returns {Object} registered provider instance
4647 *
4648 * @example
4649 * Here is an example of registering a service using
4650 * {@link auto.$provide#service $provide.service(class)}.
4651 * ```js
4652 * var Ping = function($http) {
4653 * this.$http = $http;
4654 * };
4655 *
4656 * Ping.$inject = ['$http'];
4657 *
4658 * Ping.prototype.send = function() {
4659 * return this.$http.get('/ping');
4660 * };
4661 * $provide.service('ping', Ping);
4662 * ```
4663 * You would then inject and use this service like this:
4664 * ```js
4665 * someModule.controller('Ctrl', ['ping', function(ping) {
4666 * ping.send();
4667 * }]);
4668 * ```
4669 */
4670
4671
4672/**
4673 * @ngdoc method
4674 * @name $provide#value
4675 * @description
4676 *
4677 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4678 * number, an array, an object or a function. This is short for registering a service where its
4679 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4680 * service**. That also means it is not possible to inject other services into a value service.
4681 *
4682 * Value services are similar to constant services, except that they cannot be injected into a
4683 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4684 * an Angular {@link auto.$provide#decorator decorator}.
4685 *
4686 * @param {string} name The name of the instance.
4687 * @param {*} value The value.
4688 * @returns {Object} registered provider instance
4689 *
4690 * @example
4691 * Here are some examples of creating value services.
4692 * ```js
4693 * $provide.value('ADMIN_USER', 'admin');
4694 *
4695 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4696 *
4697 * $provide.value('halfOf', function(value) {
4698 * return value / 2;
4699 * });
4700 * ```
4701 */
4702
4703
4704/**
4705 * @ngdoc method
4706 * @name $provide#constant
4707 * @description
4708 *
4709 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
4710 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
4711 * possible to inject other services into a constant.
4712 *
4713 * But unlike {@link auto.$provide#value value}, a constant can be
4714 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4715 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4716 *
4717 * @param {string} name The name of the constant.
4718 * @param {*} value The constant value.
4719 * @returns {Object} registered instance
4720 *
4721 * @example
4722 * Here a some examples of creating constants:
4723 * ```js
4724 * $provide.constant('SHARD_HEIGHT', 306);
4725 *
4726 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4727 *
4728 * $provide.constant('double', function(value) {
4729 * return value * 2;
4730 * });
4731 * ```
4732 */
4733
4734
4735/**
4736 * @ngdoc method
4737 * @name $provide#decorator
4738 * @description
4739 *
4740 * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function
4741 * intercepts the creation of a service, allowing it to override or modify the behavior of the
4742 * service. The return value of the decorator function may be the original service, or a new service
4743 * that replaces (or wraps and delegates to) the original service.
4744 *
4745 * You can find out more about using decorators in the {@link guide/decorators} guide.
4746 *
4747 * @param {string} name The name of the service to decorate.
4748 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4749 * provided and should return the decorated service instance. The function is called using
4750 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4751 * Local injection arguments:
4752 *
4753 * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured,
4754 * decorated or delegated to.
4755 *
4756 * @example
4757 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4758 * calls to {@link ng.$log#error $log.warn()}.
4759 * ```js
4760 * $provide.decorator('$log', ['$delegate', function($delegate) {
4761 * $delegate.warn = $delegate.error;
4762 * return $delegate;
4763 * }]);
4764 * ```
4765 */
4766
4767
4768function createInjector(modulesToLoad, strictDi) {
4769 strictDi = (strictDi === true);
4770 var INSTANTIATING = {},
4771 providerSuffix = 'Provider',
4772 path = [],
Ed Tanous4758d5b2017-06-06 15:28:13 -07004773 loadedModules = new NgMap(),
Ed Tanous904063f2017-03-02 16:48:24 -08004774 providerCache = {
4775 $provide: {
4776 provider: supportObject(provider),
4777 factory: supportObject(factory),
4778 service: supportObject(service),
4779 value: supportObject(value),
4780 constant: supportObject(constant),
4781 decorator: decorator
4782 }
4783 },
4784 providerInjector = (providerCache.$injector =
4785 createInternalInjector(providerCache, function(serviceName, caller) {
4786 if (angular.isString(caller)) {
4787 path.push(caller);
4788 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07004789 throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- '));
Ed Tanous904063f2017-03-02 16:48:24 -08004790 })),
4791 instanceCache = {},
4792 protoInstanceInjector =
4793 createInternalInjector(instanceCache, function(serviceName, caller) {
4794 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4795 return instanceInjector.invoke(
4796 provider.$get, provider, undefined, serviceName);
4797 }),
4798 instanceInjector = protoInstanceInjector;
4799
4800 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
Ed Tanous4758d5b2017-06-06 15:28:13 -07004801 instanceInjector.modules = providerInjector.modules = createMap();
Ed Tanous904063f2017-03-02 16:48:24 -08004802 var runBlocks = loadModules(modulesToLoad);
4803 instanceInjector = protoInstanceInjector.get('$injector');
4804 instanceInjector.strictDi = strictDi;
4805 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
4806
4807 return instanceInjector;
4808
4809 ////////////////////////////////////
4810 // $provider
4811 ////////////////////////////////////
4812
4813 function supportObject(delegate) {
4814 return function(key, value) {
4815 if (isObject(key)) {
4816 forEach(key, reverseParams(delegate));
4817 } else {
4818 return delegate(key, value);
4819 }
4820 };
4821 }
4822
4823 function provider(name, provider_) {
4824 assertNotHasOwnProperty(name, 'service');
4825 if (isFunction(provider_) || isArray(provider_)) {
4826 provider_ = providerInjector.instantiate(provider_);
4827 }
4828 if (!provider_.$get) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004829 throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name);
Ed Tanous904063f2017-03-02 16:48:24 -08004830 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07004831 return (providerCache[name + providerSuffix] = provider_);
Ed Tanous904063f2017-03-02 16:48:24 -08004832 }
4833
4834 function enforceReturnValue(name, factory) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004835 return /** @this */ function enforcedReturnValue() {
Ed Tanous904063f2017-03-02 16:48:24 -08004836 var result = instanceInjector.invoke(factory, this);
4837 if (isUndefined(result)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004838 throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name);
Ed Tanous904063f2017-03-02 16:48:24 -08004839 }
4840 return result;
4841 };
4842 }
4843
4844 function factory(name, factoryFn, enforce) {
4845 return provider(name, {
4846 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4847 });
4848 }
4849
4850 function service(name, constructor) {
4851 return factory(name, ['$injector', function($injector) {
4852 return $injector.instantiate(constructor);
4853 }]);
4854 }
4855
4856 function value(name, val) { return factory(name, valueFn(val), false); }
4857
4858 function constant(name, value) {
4859 assertNotHasOwnProperty(name, 'constant');
4860 providerCache[name] = value;
4861 instanceCache[name] = value;
4862 }
4863
4864 function decorator(serviceName, decorFn) {
4865 var origProvider = providerInjector.get(serviceName + providerSuffix),
4866 orig$get = origProvider.$get;
4867
4868 origProvider.$get = function() {
4869 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4870 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4871 };
4872 }
4873
4874 ////////////////////////////////////
4875 // Module Loading
4876 ////////////////////////////////////
4877 function loadModules(modulesToLoad) {
4878 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4879 var runBlocks = [], moduleFn;
4880 forEach(modulesToLoad, function(module) {
4881 if (loadedModules.get(module)) return;
Ed Tanous4758d5b2017-06-06 15:28:13 -07004882 loadedModules.set(module, true);
Ed Tanous904063f2017-03-02 16:48:24 -08004883
4884 function runInvokeQueue(queue) {
4885 var i, ii;
4886 for (i = 0, ii = queue.length; i < ii; i++) {
4887 var invokeArgs = queue[i],
4888 provider = providerInjector.get(invokeArgs[0]);
4889
4890 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4891 }
4892 }
4893
4894 try {
4895 if (isString(module)) {
4896 moduleFn = angularModule(module);
Ed Tanous4758d5b2017-06-06 15:28:13 -07004897 instanceInjector.modules[module] = moduleFn;
Ed Tanous904063f2017-03-02 16:48:24 -08004898 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4899 runInvokeQueue(moduleFn._invokeQueue);
4900 runInvokeQueue(moduleFn._configBlocks);
4901 } else if (isFunction(module)) {
4902 runBlocks.push(providerInjector.invoke(module));
4903 } else if (isArray(module)) {
4904 runBlocks.push(providerInjector.invoke(module));
4905 } else {
4906 assertArgFn(module, 'module');
4907 }
4908 } catch (e) {
4909 if (isArray(module)) {
4910 module = module[module.length - 1];
4911 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07004912 if (e.message && e.stack && e.stack.indexOf(e.message) === -1) {
Ed Tanous904063f2017-03-02 16:48:24 -08004913 // Safari & FF's stack traces don't contain error.message content
4914 // unlike those of Chrome and IE
4915 // So if stack doesn't contain message, we create a new string that contains both.
4916 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
Ed Tanous4758d5b2017-06-06 15:28:13 -07004917 // eslint-disable-next-line no-ex-assign
Ed Tanous904063f2017-03-02 16:48:24 -08004918 e = e.message + '\n' + e.stack;
4919 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07004920 throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}',
Ed Tanous904063f2017-03-02 16:48:24 -08004921 module, e.stack || e.message || e);
4922 }
4923 });
4924 return runBlocks;
4925 }
4926
4927 ////////////////////////////////////
4928 // internal Injector
4929 ////////////////////////////////////
4930
4931 function createInternalInjector(cache, factory) {
4932
4933 function getService(serviceName, caller) {
4934 if (cache.hasOwnProperty(serviceName)) {
4935 if (cache[serviceName] === INSTANTIATING) {
4936 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4937 serviceName + ' <- ' + path.join(' <- '));
4938 }
4939 return cache[serviceName];
4940 } else {
4941 try {
4942 path.unshift(serviceName);
4943 cache[serviceName] = INSTANTIATING;
Ed Tanous4758d5b2017-06-06 15:28:13 -07004944 cache[serviceName] = factory(serviceName, caller);
4945 return cache[serviceName];
Ed Tanous904063f2017-03-02 16:48:24 -08004946 } catch (err) {
4947 if (cache[serviceName] === INSTANTIATING) {
4948 delete cache[serviceName];
4949 }
4950 throw err;
4951 } finally {
4952 path.shift();
4953 }
4954 }
4955 }
4956
4957
4958 function injectionArgs(fn, locals, serviceName) {
4959 var args = [],
4960 $inject = createInjector.$$annotate(fn, strictDi, serviceName);
4961
4962 for (var i = 0, length = $inject.length; i < length; i++) {
4963 var key = $inject[i];
4964 if (typeof key !== 'string') {
4965 throw $injectorMinErr('itkn',
4966 'Incorrect injection token! Expected service name as string, got {0}', key);
4967 }
4968 args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
4969 getService(key, serviceName));
4970 }
4971 return args;
4972 }
4973
4974 function isClass(func) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07004975 // Support: IE 9-11 only
Ed Tanous904063f2017-03-02 16:48:24 -08004976 // IE 9-11 do not support classes and IE9 leaks with the code below.
Ed Tanous4758d5b2017-06-06 15:28:13 -07004977 if (msie || typeof func !== 'function') {
Ed Tanous904063f2017-03-02 16:48:24 -08004978 return false;
4979 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07004980 var result = func.$$ngIsClass;
4981 if (!isBoolean(result)) {
4982 // Support: Edge 12-13 only
4983 // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/
4984 result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func));
4985 }
4986 return result;
Ed Tanous904063f2017-03-02 16:48:24 -08004987 }
4988
4989 function invoke(fn, self, locals, serviceName) {
4990 if (typeof locals === 'string') {
4991 serviceName = locals;
4992 locals = null;
4993 }
4994
4995 var args = injectionArgs(fn, locals, serviceName);
4996 if (isArray(fn)) {
4997 fn = fn[fn.length - 1];
4998 }
4999
5000 if (!isClass(fn)) {
5001 // http://jsperf.com/angularjs-invoke-apply-vs-switch
5002 // #5388
5003 return fn.apply(self, args);
5004 } else {
5005 args.unshift(null);
5006 return new (Function.prototype.bind.apply(fn, args))();
5007 }
5008 }
5009
5010
5011 function instantiate(Type, locals, serviceName) {
5012 // Check if Type is annotated and use just the given function at n-1 as parameter
5013 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
5014 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
5015 var args = injectionArgs(Type, locals, serviceName);
5016 // Empty object at position 0 is ignored for invocation with `new`, but required.
5017 args.unshift(null);
5018 return new (Function.prototype.bind.apply(ctor, args))();
5019 }
5020
5021
5022 return {
5023 invoke: invoke,
5024 instantiate: instantiate,
5025 get: getService,
5026 annotate: createInjector.$$annotate,
5027 has: function(name) {
5028 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
5029 }
5030 };
5031 }
5032}
5033
5034createInjector.$$annotate = annotate;
5035
5036/**
5037 * @ngdoc provider
5038 * @name $anchorScrollProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -07005039 * @this
Ed Tanous904063f2017-03-02 16:48:24 -08005040 *
5041 * @description
5042 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
5043 * {@link ng.$location#hash $location.hash()} changes.
5044 */
5045function $AnchorScrollProvider() {
5046
5047 var autoScrollingEnabled = true;
5048
5049 /**
5050 * @ngdoc method
5051 * @name $anchorScrollProvider#disableAutoScrolling
5052 *
5053 * @description
5054 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
5055 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
5056 * Use this method to disable automatic scrolling.
5057 *
5058 * If automatic scrolling is disabled, one must explicitly call
5059 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
5060 * current hash.
5061 */
5062 this.disableAutoScrolling = function() {
5063 autoScrollingEnabled = false;
5064 };
5065
5066 /**
5067 * @ngdoc service
5068 * @name $anchorScroll
5069 * @kind function
5070 * @requires $window
5071 * @requires $location
5072 * @requires $rootScope
5073 *
5074 * @description
5075 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
5076 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
5077 * in the
5078 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
5079 *
5080 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
5081 * match any anchor whenever it changes. This can be disabled by calling
5082 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
5083 *
5084 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
5085 * vertical scroll-offset (either fixed or dynamic).
5086 *
5087 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
5088 * {@link ng.$location#hash $location.hash()} will be used.
5089 *
5090 * @property {(number|function|jqLite)} yOffset
5091 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
5092 * positioned elements at the top of the page, such as navbars, headers etc.
5093 *
5094 * `yOffset` can be specified in various ways:
5095 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
5096 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
5097 * a number representing the offset (in pixels).<br /><br />
5098 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
5099 * the top of the page to the element's bottom will be used as offset.<br />
5100 * **Note**: The element will be taken into account only as long as its `position` is set to
5101 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
5102 * their height and/or positioning according to the viewport's size.
5103 *
5104 * <br />
5105 * <div class="alert alert-warning">
5106 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
5107 * not some child element.
5108 * </div>
5109 *
5110 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -07005111 <example module="anchorScrollExample" name="anchor-scroll">
Ed Tanous904063f2017-03-02 16:48:24 -08005112 <file name="index.html">
5113 <div id="scrollArea" ng-controller="ScrollController">
5114 <a ng-click="gotoBottom()">Go to bottom</a>
5115 <a id="bottom"></a> You're at the bottom!
5116 </div>
5117 </file>
5118 <file name="script.js">
5119 angular.module('anchorScrollExample', [])
5120 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
Ed Tanous4758d5b2017-06-06 15:28:13 -07005121 function($scope, $location, $anchorScroll) {
Ed Tanous904063f2017-03-02 16:48:24 -08005122 $scope.gotoBottom = function() {
5123 // set the location.hash to the id of
5124 // the element you wish to scroll to.
5125 $location.hash('bottom');
5126
5127 // call $anchorScroll()
5128 $anchorScroll();
5129 };
5130 }]);
5131 </file>
5132 <file name="style.css">
5133 #scrollArea {
5134 height: 280px;
5135 overflow: auto;
5136 }
5137
5138 #bottom {
5139 display: block;
5140 margin-top: 2000px;
5141 }
5142 </file>
5143 </example>
5144 *
5145 * <hr />
5146 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
5147 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
5148 *
5149 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -07005150 <example module="anchorScrollOffsetExample" name="anchor-scroll-offset">
Ed Tanous904063f2017-03-02 16:48:24 -08005151 <file name="index.html">
5152 <div class="fixed-header" ng-controller="headerCtrl">
5153 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
5154 Go to anchor {{x}}
5155 </a>
5156 </div>
5157 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
5158 Anchor {{x}} of 5
5159 </div>
5160 </file>
5161 <file name="script.js">
5162 angular.module('anchorScrollOffsetExample', [])
5163 .run(['$anchorScroll', function($anchorScroll) {
5164 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
5165 }])
5166 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
Ed Tanous4758d5b2017-06-06 15:28:13 -07005167 function($anchorScroll, $location, $scope) {
Ed Tanous904063f2017-03-02 16:48:24 -08005168 $scope.gotoAnchor = function(x) {
5169 var newHash = 'anchor' + x;
5170 if ($location.hash() !== newHash) {
5171 // set the $location.hash to `newHash` and
5172 // $anchorScroll will automatically scroll to it
5173 $location.hash('anchor' + x);
5174 } else {
5175 // call $anchorScroll() explicitly,
5176 // since $location.hash hasn't changed
5177 $anchorScroll();
5178 }
5179 };
5180 }
5181 ]);
5182 </file>
5183 <file name="style.css">
5184 body {
5185 padding-top: 50px;
5186 }
5187
5188 .anchor {
5189 border: 2px dashed DarkOrchid;
5190 padding: 10px 10px 200px 10px;
5191 }
5192
5193 .fixed-header {
5194 background-color: rgba(0, 0, 0, 0.2);
5195 height: 50px;
5196 position: fixed;
5197 top: 0; left: 0; right: 0;
5198 }
5199
5200 .fixed-header > a {
5201 display: inline-block;
5202 margin: 5px 15px;
5203 }
5204 </file>
5205 </example>
5206 */
5207 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
5208 var document = $window.document;
5209
5210 // Helper function to get first anchor from a NodeList
5211 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
5212 // and working in all supported browsers.)
5213 function getFirstAnchor(list) {
5214 var result = null;
5215 Array.prototype.some.call(list, function(element) {
5216 if (nodeName_(element) === 'a') {
5217 result = element;
5218 return true;
5219 }
5220 });
5221 return result;
5222 }
5223
5224 function getYOffset() {
5225
5226 var offset = scroll.yOffset;
5227
5228 if (isFunction(offset)) {
5229 offset = offset();
5230 } else if (isElement(offset)) {
5231 var elem = offset[0];
5232 var style = $window.getComputedStyle(elem);
5233 if (style.position !== 'fixed') {
5234 offset = 0;
5235 } else {
5236 offset = elem.getBoundingClientRect().bottom;
5237 }
5238 } else if (!isNumber(offset)) {
5239 offset = 0;
5240 }
5241
5242 return offset;
5243 }
5244
5245 function scrollTo(elem) {
5246 if (elem) {
5247 elem.scrollIntoView();
5248
5249 var offset = getYOffset();
5250
5251 if (offset) {
5252 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
5253 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
5254 // top of the viewport.
5255 //
5256 // IF the number of pixels from the top of `elem` to the end of the page's content is less
5257 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
5258 // way down the page.
5259 //
5260 // This is often the case for elements near the bottom of the page.
5261 //
5262 // In such cases we do not need to scroll the whole `offset` up, just the difference between
5263 // the top of the element and the offset, which is enough to align the top of `elem` at the
5264 // desired position.
5265 var elemTop = elem.getBoundingClientRect().top;
5266 $window.scrollBy(0, elemTop - offset);
5267 }
5268 } else {
5269 $window.scrollTo(0, 0);
5270 }
5271 }
5272
5273 function scroll(hash) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005274 // Allow numeric hashes
5275 hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash();
Ed Tanous904063f2017-03-02 16:48:24 -08005276 var elm;
5277
5278 // empty hash, scroll to the top of the page
5279 if (!hash) scrollTo(null);
5280
5281 // element with given id
5282 else if ((elm = document.getElementById(hash))) scrollTo(elm);
5283
5284 // first anchor with given name :-D
5285 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
5286
Ed Tanous4758d5b2017-06-06 15:28:13 -07005287 // no element and hash === 'top', scroll to the top of the page
Ed Tanous904063f2017-03-02 16:48:24 -08005288 else if (hash === 'top') scrollTo(null);
5289 }
5290
5291 // does not scroll when user clicks on anchor link that is currently on
5292 // (no url change, no $location.hash() change), browser native does scroll
5293 if (autoScrollingEnabled) {
5294 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
5295 function autoScrollWatchAction(newVal, oldVal) {
5296 // skip the initial scroll if $location.hash is empty
5297 if (newVal === oldVal && newVal === '') return;
5298
5299 jqLiteDocumentLoaded(function() {
5300 $rootScope.$evalAsync(scroll);
5301 });
5302 });
5303 }
5304
5305 return scroll;
5306 }];
5307}
5308
5309var $animateMinErr = minErr('$animate');
5310var ELEMENT_NODE = 1;
5311var NG_ANIMATE_CLASSNAME = 'ng-animate';
5312
5313function mergeClasses(a,b) {
5314 if (!a && !b) return '';
5315 if (!a) return b;
5316 if (!b) return a;
5317 if (isArray(a)) a = a.join(' ');
5318 if (isArray(b)) b = b.join(' ');
5319 return a + ' ' + b;
5320}
5321
5322function extractElementNode(element) {
5323 for (var i = 0; i < element.length; i++) {
5324 var elm = element[i];
5325 if (elm.nodeType === ELEMENT_NODE) {
5326 return elm;
5327 }
5328 }
5329}
5330
5331function splitClasses(classes) {
5332 if (isString(classes)) {
5333 classes = classes.split(' ');
5334 }
5335
5336 // Use createMap() to prevent class assumptions involving property names in
5337 // Object.prototype
5338 var obj = createMap();
5339 forEach(classes, function(klass) {
5340 // sometimes the split leaves empty string values
5341 // incase extra spaces were applied to the options
5342 if (klass.length) {
5343 obj[klass] = true;
5344 }
5345 });
5346 return obj;
5347}
5348
5349// if any other type of options value besides an Object value is
5350// passed into the $animate.method() animation then this helper code
5351// will be run which will ignore it. While this patch is not the
5352// greatest solution to this, a lot of existing plugins depend on
5353// $animate to either call the callback (< 1.2) or return a promise
5354// that can be changed. This helper function ensures that the options
5355// are wiped clean incase a callback function is provided.
5356function prepareAnimateOptions(options) {
5357 return isObject(options)
5358 ? options
5359 : {};
5360}
5361
Ed Tanous4758d5b2017-06-06 15:28:13 -07005362var $$CoreAnimateJsProvider = /** @this */ function() {
Ed Tanous904063f2017-03-02 16:48:24 -08005363 this.$get = noop;
5364};
5365
5366// this is prefixed with Core since it conflicts with
5367// the animateQueueProvider defined in ngAnimate/animateQueue.js
Ed Tanous4758d5b2017-06-06 15:28:13 -07005368var $$CoreAnimateQueueProvider = /** @this */ function() {
5369 var postDigestQueue = new NgMap();
Ed Tanous904063f2017-03-02 16:48:24 -08005370 var postDigestElements = [];
5371
5372 this.$get = ['$$AnimateRunner', '$rootScope',
5373 function($$AnimateRunner, $rootScope) {
5374 return {
5375 enabled: noop,
5376 on: noop,
5377 off: noop,
5378 pin: noop,
5379
5380 push: function(element, event, options, domOperation) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005381 if (domOperation) {
5382 domOperation();
5383 }
Ed Tanous904063f2017-03-02 16:48:24 -08005384
5385 options = options || {};
Ed Tanous4758d5b2017-06-06 15:28:13 -07005386 if (options.from) {
5387 element.css(options.from);
5388 }
5389 if (options.to) {
5390 element.css(options.to);
5391 }
Ed Tanous904063f2017-03-02 16:48:24 -08005392
5393 if (options.addClass || options.removeClass) {
5394 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5395 }
5396
Ed Tanous4758d5b2017-06-06 15:28:13 -07005397 var runner = new $$AnimateRunner();
Ed Tanous904063f2017-03-02 16:48:24 -08005398
5399 // since there are no animations to run the runner needs to be
5400 // notified that the animation call is complete.
5401 runner.complete();
5402 return runner;
5403 }
5404 };
5405
5406
5407 function updateData(data, classes, value) {
5408 var changed = false;
5409 if (classes) {
5410 classes = isString(classes) ? classes.split(' ') :
5411 isArray(classes) ? classes : [];
5412 forEach(classes, function(className) {
5413 if (className) {
5414 changed = true;
5415 data[className] = value;
5416 }
5417 });
5418 }
5419 return changed;
5420 }
5421
5422 function handleCSSClassChanges() {
5423 forEach(postDigestElements, function(element) {
5424 var data = postDigestQueue.get(element);
5425 if (data) {
5426 var existing = splitClasses(element.attr('class'));
5427 var toAdd = '';
5428 var toRemove = '';
5429 forEach(data, function(status, className) {
5430 var hasClass = !!existing[className];
5431 if (status !== hasClass) {
5432 if (status) {
5433 toAdd += (toAdd.length ? ' ' : '') + className;
5434 } else {
5435 toRemove += (toRemove.length ? ' ' : '') + className;
5436 }
5437 }
5438 });
5439
5440 forEach(element, function(elm) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005441 if (toAdd) {
5442 jqLiteAddClass(elm, toAdd);
5443 }
5444 if (toRemove) {
5445 jqLiteRemoveClass(elm, toRemove);
5446 }
Ed Tanous904063f2017-03-02 16:48:24 -08005447 });
Ed Tanous4758d5b2017-06-06 15:28:13 -07005448 postDigestQueue.delete(element);
Ed Tanous904063f2017-03-02 16:48:24 -08005449 }
5450 });
5451 postDigestElements.length = 0;
5452 }
5453
5454
5455 function addRemoveClassesPostDigest(element, add, remove) {
5456 var data = postDigestQueue.get(element) || {};
5457
5458 var classesAdded = updateData(data, add, true);
5459 var classesRemoved = updateData(data, remove, false);
5460
5461 if (classesAdded || classesRemoved) {
5462
Ed Tanous4758d5b2017-06-06 15:28:13 -07005463 postDigestQueue.set(element, data);
Ed Tanous904063f2017-03-02 16:48:24 -08005464 postDigestElements.push(element);
5465
5466 if (postDigestElements.length === 1) {
5467 $rootScope.$$postDigest(handleCSSClassChanges);
5468 }
5469 }
5470 }
5471 }];
5472};
5473
5474/**
5475 * @ngdoc provider
5476 * @name $animateProvider
5477 *
5478 * @description
5479 * Default implementation of $animate that doesn't perform any animations, instead just
5480 * synchronously performs DOM updates and resolves the returned runner promise.
5481 *
5482 * In order to enable animations the `ngAnimate` module has to be loaded.
5483 *
5484 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5485 */
Ed Tanous4758d5b2017-06-06 15:28:13 -07005486var $AnimateProvider = ['$provide', /** @this */ function($provide) {
Ed Tanous904063f2017-03-02 16:48:24 -08005487 var provider = this;
Ed Tanous4758d5b2017-06-06 15:28:13 -07005488 var classNameFilter = null;
Ed Tanous904063f2017-03-02 16:48:24 -08005489
5490 this.$$registeredAnimations = Object.create(null);
5491
5492 /**
5493 * @ngdoc method
5494 * @name $animateProvider#register
5495 *
5496 * @description
5497 * Registers a new injectable animation factory function. The factory function produces the
5498 * animation object which contains callback functions for each event that is expected to be
5499 * animated.
5500 *
5501 * * `eventFn`: `function(element, ... , doneFunction, options)`
5502 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5503 * on the type of animation additional arguments will be injected into the animation function. The
5504 * list below explains the function signatures for the different animation methods:
5505 *
5506 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5507 * - addClass: function(element, addedClasses, doneFunction, options)
5508 * - removeClass: function(element, removedClasses, doneFunction, options)
5509 * - enter, leave, move: function(element, doneFunction, options)
5510 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5511 *
5512 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5513 *
5514 * ```js
5515 * return {
5516 * //enter, leave, move signature
5517 * eventFn : function(element, done, options) {
5518 * //code to run the animation
5519 * //once complete, then run done()
5520 * return function endFunction(wasCancelled) {
5521 * //code to cancel the animation
5522 * }
5523 * }
5524 * }
5525 * ```
5526 *
5527 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5528 * @param {Function} factory The factory function that will be executed to return the animation
5529 * object.
5530 */
5531 this.register = function(name, factory) {
5532 if (name && name.charAt(0) !== '.') {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005533 throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name);
Ed Tanous904063f2017-03-02 16:48:24 -08005534 }
5535
5536 var key = name + '-animation';
5537 provider.$$registeredAnimations[name.substr(1)] = key;
5538 $provide.factory(key, factory);
5539 };
5540
5541 /**
5542 * @ngdoc method
5543 * @name $animateProvider#classNameFilter
5544 *
5545 * @description
5546 * Sets and/or returns the CSS class regular expression that is checked when performing
5547 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5548 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5549 * When setting the `classNameFilter` value, animations will only be performed on elements
5550 * that successfully match the filter expression. This in turn can boost performance
5551 * for low-powered devices as well as applications containing a lot of structural operations.
5552 * @param {RegExp=} expression The className expression which will be checked against all animations
5553 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5554 */
5555 this.classNameFilter = function(expression) {
5556 if (arguments.length === 1) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005557 classNameFilter = (expression instanceof RegExp) ? expression : null;
5558 if (classNameFilter) {
5559 var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]');
5560 if (reservedRegex.test(classNameFilter.toString())) {
5561 classNameFilter = null;
5562 throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
Ed Tanous904063f2017-03-02 16:48:24 -08005563 }
5564 }
5565 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07005566 return classNameFilter;
Ed Tanous904063f2017-03-02 16:48:24 -08005567 };
5568
5569 this.$get = ['$$animateQueue', function($$animateQueue) {
5570 function domInsert(element, parentElement, afterElement) {
5571 // if for some reason the previous element was removed
5572 // from the dom sometime before this code runs then let's
5573 // just stick to using the parent element as the anchor
5574 if (afterElement) {
5575 var afterNode = extractElementNode(afterElement);
5576 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5577 afterElement = null;
5578 }
5579 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07005580 if (afterElement) {
5581 afterElement.after(element);
5582 } else {
5583 parentElement.prepend(element);
5584 }
Ed Tanous904063f2017-03-02 16:48:24 -08005585 }
5586
5587 /**
5588 * @ngdoc service
5589 * @name $animate
5590 * @description The $animate service exposes a series of DOM utility methods that provide support
5591 * for animation hooks. The default behavior is the application of DOM operations, however,
5592 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5593 * to ensure that animation runs with the triggered DOM operation.
5594 *
5595 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5596 * included and only when it is active then the animation hooks that `$animate` triggers will be
5597 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5598 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5599 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5600 *
5601 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5602 *
5603 * To learn more about enabling animation support, click here to visit the
5604 * {@link ngAnimate ngAnimate module page}.
5605 */
5606 return {
5607 // we don't call it directly since non-existant arguments may
5608 // be interpreted as null within the sub enabled function
5609
5610 /**
5611 *
5612 * @ngdoc method
5613 * @name $animate#on
5614 * @kind function
5615 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5616 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5617 * is fired with the following params:
5618 *
5619 * ```js
5620 * $animate.on('enter', container,
5621 * function callback(element, phase) {
5622 * // cool we detected an enter animation within the container
5623 * }
5624 * );
5625 * ```
5626 *
5627 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5628 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5629 * as well as among its children
5630 * @param {Function} callback the callback function that will be fired when the listener is triggered
5631 *
5632 * The arguments present in the callback function are:
5633 * * `element` - The captured DOM element that the animation was fired on.
5634 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5635 */
5636 on: $$animateQueue.on,
5637
5638 /**
5639 *
5640 * @ngdoc method
5641 * @name $animate#off
5642 * @kind function
5643 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5644 * can be used in three different ways depending on the arguments:
5645 *
5646 * ```js
5647 * // remove all the animation event listeners listening for `enter`
5648 * $animate.off('enter');
5649 *
5650 * // remove listeners for all animation events from the container element
5651 * $animate.off(container);
5652 *
5653 * // remove all the animation event listeners listening for `enter` on the given element and its children
5654 * $animate.off('enter', container);
5655 *
5656 * // remove the event listener function provided by `callback` that is set
5657 * // to listen for `enter` on the given `container` as well as its children
5658 * $animate.off('enter', container, callback);
5659 * ```
5660 *
5661 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
5662 * addClass, removeClass, etc...), or the container element. If it is the element, all other
5663 * arguments are ignored.
5664 * @param {DOMElement=} container the container element the event listener was placed on
5665 * @param {Function=} callback the callback function that was registered as the listener
5666 */
5667 off: $$animateQueue.off,
5668
5669 /**
5670 * @ngdoc method
5671 * @name $animate#pin
5672 * @kind function
5673 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5674 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5675 * element despite being outside the realm of the application or within another application. Say for example if the application
5676 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5677 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5678 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5679 *
5680 * Note that this feature is only active when the `ngAnimate` module is used.
5681 *
5682 * @param {DOMElement} element the external element that will be pinned
5683 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5684 */
5685 pin: $$animateQueue.pin,
5686
5687 /**
5688 *
5689 * @ngdoc method
5690 * @name $animate#enabled
5691 * @kind function
5692 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5693 * function can be called in four ways:
5694 *
5695 * ```js
5696 * // returns true or false
5697 * $animate.enabled();
5698 *
5699 * // changes the enabled state for all animations
5700 * $animate.enabled(false);
5701 * $animate.enabled(true);
5702 *
5703 * // returns true or false if animations are enabled for an element
5704 * $animate.enabled(element);
5705 *
5706 * // changes the enabled state for an element and its children
5707 * $animate.enabled(element, true);
5708 * $animate.enabled(element, false);
5709 * ```
5710 *
5711 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5712 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5713 *
5714 * @return {boolean} whether or not animations are enabled
5715 */
5716 enabled: $$animateQueue.enabled,
5717
5718 /**
5719 * @ngdoc method
5720 * @name $animate#cancel
5721 * @kind function
5722 * @description Cancels the provided animation.
5723 *
5724 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5725 */
5726 cancel: function(runner) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005727 if (runner.end) {
5728 runner.end();
5729 }
Ed Tanous904063f2017-03-02 16:48:24 -08005730 },
5731
5732 /**
5733 *
5734 * @ngdoc method
5735 * @name $animate#enter
5736 * @kind function
5737 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5738 * as the first child within the `parent` element and then triggers an animation.
5739 * A promise is returned that will be resolved during the next digest once the animation
5740 * has completed.
5741 *
5742 * @param {DOMElement} element the element which will be inserted into the DOM
5743 * @param {DOMElement} parent the parent element which will append the element as
5744 * a child (so long as the after element is not present)
5745 * @param {DOMElement=} after the sibling element after which the element will be appended
5746 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5747 * The object can have the following properties:
5748 *
5749 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5750 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5751 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5752 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5753 *
5754 * @return {Promise} the animation callback promise
5755 */
5756 enter: function(element, parent, after, options) {
5757 parent = parent && jqLite(parent);
5758 after = after && jqLite(after);
5759 parent = parent || after.parent();
5760 domInsert(element, parent, after);
5761 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5762 },
5763
5764 /**
5765 *
5766 * @ngdoc method
5767 * @name $animate#move
5768 * @kind function
5769 * @description Inserts (moves) the element into its new position in the DOM either after
5770 * the `after` element (if provided) or as the first child within the `parent` element
5771 * and then triggers an animation. A promise is returned that will be resolved
5772 * during the next digest once the animation has completed.
5773 *
5774 * @param {DOMElement} element the element which will be moved into the new DOM position
5775 * @param {DOMElement} parent the parent element which will append the element as
5776 * a child (so long as the after element is not present)
5777 * @param {DOMElement=} after the sibling element after which the element will be appended
5778 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5779 * The object can have the following properties:
5780 *
5781 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5782 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5783 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5784 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5785 *
5786 * @return {Promise} the animation callback promise
5787 */
5788 move: function(element, parent, after, options) {
5789 parent = parent && jqLite(parent);
5790 after = after && jqLite(after);
5791 parent = parent || after.parent();
5792 domInsert(element, parent, after);
5793 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5794 },
5795
5796 /**
5797 * @ngdoc method
5798 * @name $animate#leave
5799 * @kind function
5800 * @description Triggers an animation and then removes the element from the DOM.
5801 * When the function is called a promise is returned that will be resolved during the next
5802 * digest once the animation has completed.
5803 *
5804 * @param {DOMElement} element the element which will be removed from the DOM
5805 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5806 * The object can have the following properties:
5807 *
5808 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5809 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5810 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5811 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5812 *
5813 * @return {Promise} the animation callback promise
5814 */
5815 leave: function(element, options) {
5816 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5817 element.remove();
5818 });
5819 },
5820
5821 /**
5822 * @ngdoc method
5823 * @name $animate#addClass
5824 * @kind function
5825 *
5826 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5827 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5828 * animation if element already contains the CSS class or if the class is removed at a later step.
5829 * Note that class-based animations are treated differently compared to structural animations
5830 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5831 * depending if CSS or JavaScript animations are used.
5832 *
5833 * @param {DOMElement} element the element which the CSS classes will be applied to
5834 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5835 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5836 * The object can have the following properties:
5837 *
5838 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5839 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5840 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5841 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5842 *
5843 * @return {Promise} the animation callback promise
5844 */
5845 addClass: function(element, className, options) {
5846 options = prepareAnimateOptions(options);
5847 options.addClass = mergeClasses(options.addclass, className);
5848 return $$animateQueue.push(element, 'addClass', options);
5849 },
5850
5851 /**
5852 * @ngdoc method
5853 * @name $animate#removeClass
5854 * @kind function
5855 *
5856 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5857 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5858 * animation if element does not contain the CSS class or if the class is added at a later step.
5859 * Note that class-based animations are treated differently compared to structural animations
5860 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5861 * depending if CSS or JavaScript animations are used.
5862 *
5863 * @param {DOMElement} element the element which the CSS classes will be applied to
5864 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5865 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5866 * The object can have the following properties:
5867 *
5868 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5869 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5870 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5871 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5872 *
5873 * @return {Promise} the animation callback promise
5874 */
5875 removeClass: function(element, className, options) {
5876 options = prepareAnimateOptions(options);
5877 options.removeClass = mergeClasses(options.removeClass, className);
5878 return $$animateQueue.push(element, 'removeClass', options);
5879 },
5880
5881 /**
5882 * @ngdoc method
5883 * @name $animate#setClass
5884 * @kind function
5885 *
5886 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5887 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5888 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5889 * passed. Note that class-based animations are treated differently compared to structural animations
5890 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5891 * depending if CSS or JavaScript animations are used.
5892 *
5893 * @param {DOMElement} element the element which the CSS classes will be applied to
5894 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5895 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5896 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5897 * The object can have the following properties:
5898 *
5899 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5900 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5901 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5902 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5903 *
5904 * @return {Promise} the animation callback promise
5905 */
5906 setClass: function(element, add, remove, options) {
5907 options = prepareAnimateOptions(options);
5908 options.addClass = mergeClasses(options.addClass, add);
5909 options.removeClass = mergeClasses(options.removeClass, remove);
5910 return $$animateQueue.push(element, 'setClass', options);
5911 },
5912
5913 /**
5914 * @ngdoc method
5915 * @name $animate#animate
5916 * @kind function
5917 *
5918 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5919 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
Ed Tanous4758d5b2017-06-06 15:28:13 -07005920 * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
Ed Tanous904063f2017-03-02 16:48:24 -08005921 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
5922 * style in `to`, the style in `from` is applied immediately, and no animation is run.
5923 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
5924 * method (or as part of the `options` parameter):
5925 *
5926 * ```js
5927 * ngModule.animation('.my-inline-animation', function() {
5928 * return {
5929 * animate : function(element, from, to, done, options) {
5930 * //animation
5931 * done();
5932 * }
5933 * }
5934 * });
5935 * ```
5936 *
5937 * @param {DOMElement} element the element which the CSS styles will be applied to
5938 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5939 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5940 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5941 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5942 * (Note that if no animation is detected then this value will not be applied to the element.)
5943 * @param {object=} options an optional collection of options/styles that will be applied to the element.
5944 * The object can have the following properties:
5945 *
5946 * - **addClass** - `{string}` - space-separated CSS classes to add to element
5947 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5948 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5949 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5950 *
5951 * @return {Promise} the animation callback promise
5952 */
5953 animate: function(element, from, to, className, options) {
5954 options = prepareAnimateOptions(options);
5955 options.from = options.from ? extend(options.from, from) : from;
5956 options.to = options.to ? extend(options.to, to) : to;
5957
5958 className = className || 'ng-inline-animate';
5959 options.tempClasses = mergeClasses(options.tempClasses, className);
5960 return $$animateQueue.push(element, 'animate', options);
5961 }
5962 };
5963 }];
5964}];
5965
Ed Tanous4758d5b2017-06-06 15:28:13 -07005966var $$AnimateAsyncRunFactoryProvider = /** @this */ function() {
Ed Tanous904063f2017-03-02 16:48:24 -08005967 this.$get = ['$$rAF', function($$rAF) {
5968 var waitQueue = [];
5969
5970 function waitForTick(fn) {
5971 waitQueue.push(fn);
5972 if (waitQueue.length > 1) return;
5973 $$rAF(function() {
5974 for (var i = 0; i < waitQueue.length; i++) {
5975 waitQueue[i]();
5976 }
5977 waitQueue = [];
5978 });
5979 }
5980
5981 return function() {
5982 var passed = false;
5983 waitForTick(function() {
5984 passed = true;
5985 });
5986 return function(callback) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07005987 if (passed) {
5988 callback();
5989 } else {
5990 waitForTick(callback);
5991 }
Ed Tanous904063f2017-03-02 16:48:24 -08005992 };
5993 };
5994 }];
5995};
5996
Ed Tanous4758d5b2017-06-06 15:28:13 -07005997var $$AnimateRunnerFactoryProvider = /** @this */ function() {
5998 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout',
5999 function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) {
Ed Tanous904063f2017-03-02 16:48:24 -08006000
6001 var INITIAL_STATE = 0;
6002 var DONE_PENDING_STATE = 1;
6003 var DONE_COMPLETE_STATE = 2;
6004
6005 AnimateRunner.chain = function(chain, callback) {
6006 var index = 0;
6007
6008 next();
6009 function next() {
6010 if (index === chain.length) {
6011 callback(true);
6012 return;
6013 }
6014
6015 chain[index](function(response) {
6016 if (response === false) {
6017 callback(false);
6018 return;
6019 }
6020 index++;
6021 next();
6022 });
6023 }
6024 };
6025
6026 AnimateRunner.all = function(runners, callback) {
6027 var count = 0;
6028 var status = true;
6029 forEach(runners, function(runner) {
6030 runner.done(onProgress);
6031 });
6032
6033 function onProgress(response) {
6034 status = status && response;
6035 if (++count === runners.length) {
6036 callback(status);
6037 }
6038 }
6039 };
6040
6041 function AnimateRunner(host) {
6042 this.setHost(host);
6043
6044 var rafTick = $$animateAsyncRun();
6045 var timeoutTick = function(fn) {
6046 $timeout(fn, 0, false);
6047 };
6048
6049 this._doneCallbacks = [];
6050 this._tick = function(fn) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006051 if ($$isDocumentHidden()) {
Ed Tanous904063f2017-03-02 16:48:24 -08006052 timeoutTick(fn);
6053 } else {
6054 rafTick(fn);
6055 }
6056 };
6057 this._state = 0;
6058 }
6059
6060 AnimateRunner.prototype = {
6061 setHost: function(host) {
6062 this.host = host || {};
6063 },
6064
6065 done: function(fn) {
6066 if (this._state === DONE_COMPLETE_STATE) {
6067 fn();
6068 } else {
6069 this._doneCallbacks.push(fn);
6070 }
6071 },
6072
6073 progress: noop,
6074
6075 getPromise: function() {
6076 if (!this.promise) {
6077 var self = this;
6078 this.promise = $q(function(resolve, reject) {
6079 self.done(function(status) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006080 if (status === false) {
6081 reject();
6082 } else {
6083 resolve();
6084 }
Ed Tanous904063f2017-03-02 16:48:24 -08006085 });
6086 });
6087 }
6088 return this.promise;
6089 },
6090
6091 then: function(resolveHandler, rejectHandler) {
6092 return this.getPromise().then(resolveHandler, rejectHandler);
6093 },
6094
6095 'catch': function(handler) {
6096 return this.getPromise()['catch'](handler);
6097 },
6098
6099 'finally': function(handler) {
6100 return this.getPromise()['finally'](handler);
6101 },
6102
6103 pause: function() {
6104 if (this.host.pause) {
6105 this.host.pause();
6106 }
6107 },
6108
6109 resume: function() {
6110 if (this.host.resume) {
6111 this.host.resume();
6112 }
6113 },
6114
6115 end: function() {
6116 if (this.host.end) {
6117 this.host.end();
6118 }
6119 this._resolve(true);
6120 },
6121
6122 cancel: function() {
6123 if (this.host.cancel) {
6124 this.host.cancel();
6125 }
6126 this._resolve(false);
6127 },
6128
6129 complete: function(response) {
6130 var self = this;
6131 if (self._state === INITIAL_STATE) {
6132 self._state = DONE_PENDING_STATE;
6133 self._tick(function() {
6134 self._resolve(response);
6135 });
6136 }
6137 },
6138
6139 _resolve: function(response) {
6140 if (this._state !== DONE_COMPLETE_STATE) {
6141 forEach(this._doneCallbacks, function(fn) {
6142 fn(response);
6143 });
6144 this._doneCallbacks.length = 0;
6145 this._state = DONE_COMPLETE_STATE;
6146 }
6147 }
6148 };
6149
6150 return AnimateRunner;
6151 }];
6152};
6153
Ed Tanous4758d5b2017-06-06 15:28:13 -07006154/* exported $CoreAnimateCssProvider */
6155
Ed Tanous904063f2017-03-02 16:48:24 -08006156/**
6157 * @ngdoc service
6158 * @name $animateCss
6159 * @kind object
Ed Tanous4758d5b2017-06-06 15:28:13 -07006160 * @this
Ed Tanous904063f2017-03-02 16:48:24 -08006161 *
6162 * @description
6163 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
6164 * then the `$animateCss` service will actually perform animations.
6165 *
6166 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
6167 */
6168var $CoreAnimateCssProvider = function() {
6169 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
6170
6171 return function(element, initialOptions) {
6172 // all of the animation functions should create
6173 // a copy of the options data, however, if a
6174 // parent service has already created a copy then
6175 // we should stick to using that
6176 var options = initialOptions || {};
6177 if (!options.$$prepared) {
6178 options = copy(options);
6179 }
6180
6181 // there is no point in applying the styles since
6182 // there is no animation that goes on at all in
6183 // this version of $animateCss.
6184 if (options.cleanupStyles) {
6185 options.from = options.to = null;
6186 }
6187
6188 if (options.from) {
6189 element.css(options.from);
6190 options.from = null;
6191 }
6192
Ed Tanous904063f2017-03-02 16:48:24 -08006193 var closed, runner = new $$AnimateRunner();
6194 return {
6195 start: run,
6196 end: run
6197 };
6198
6199 function run() {
6200 $$rAF(function() {
6201 applyAnimationContents();
6202 if (!closed) {
6203 runner.complete();
6204 }
6205 closed = true;
6206 });
6207 return runner;
6208 }
6209
6210 function applyAnimationContents() {
6211 if (options.addClass) {
6212 element.addClass(options.addClass);
6213 options.addClass = null;
6214 }
6215 if (options.removeClass) {
6216 element.removeClass(options.removeClass);
6217 options.removeClass = null;
6218 }
6219 if (options.to) {
6220 element.css(options.to);
6221 options.to = null;
6222 }
6223 }
6224 };
6225 }];
6226};
6227
6228/* global stripHash: true */
6229
6230/**
6231 * ! This is a private undocumented service !
6232 *
6233 * @name $browser
6234 * @requires $log
6235 * @description
6236 * This object has two goals:
6237 *
6238 * - hide all the global state in the browser caused by the window object
6239 * - abstract away all the browser specific features and inconsistencies
6240 *
6241 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
6242 * service, which can be used for convenient testing of the application without the interaction with
6243 * the real browser apis.
6244 */
6245/**
6246 * @param {object} window The global window object.
6247 * @param {object} document jQuery wrapped document.
6248 * @param {object} $log window.console or an object with the same interface.
6249 * @param {object} $sniffer $sniffer service
6250 */
6251function Browser(window, document, $log, $sniffer) {
6252 var self = this,
6253 location = window.location,
6254 history = window.history,
6255 setTimeout = window.setTimeout,
6256 clearTimeout = window.clearTimeout,
6257 pendingDeferIds = {};
6258
6259 self.isMock = false;
6260
6261 var outstandingRequestCount = 0;
6262 var outstandingRequestCallbacks = [];
6263
6264 // TODO(vojta): remove this temporary api
6265 self.$$completeOutstandingRequest = completeOutstandingRequest;
6266 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
6267
6268 /**
6269 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
6270 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
6271 */
6272 function completeOutstandingRequest(fn) {
6273 try {
6274 fn.apply(null, sliceArgs(arguments, 1));
6275 } finally {
6276 outstandingRequestCount--;
6277 if (outstandingRequestCount === 0) {
6278 while (outstandingRequestCallbacks.length) {
6279 try {
6280 outstandingRequestCallbacks.pop()();
6281 } catch (e) {
6282 $log.error(e);
6283 }
6284 }
6285 }
6286 }
6287 }
6288
6289 function getHash(url) {
6290 var index = url.indexOf('#');
6291 return index === -1 ? '' : url.substr(index);
6292 }
6293
6294 /**
6295 * @private
6296 * Note: this method is used only by scenario runner
6297 * TODO(vojta): prefix this method with $$ ?
6298 * @param {function()} callback Function that will be called when no outstanding request
6299 */
6300 self.notifyWhenNoOutstandingRequests = function(callback) {
6301 if (outstandingRequestCount === 0) {
6302 callback();
6303 } else {
6304 outstandingRequestCallbacks.push(callback);
6305 }
6306 };
6307
6308 //////////////////////////////////////////////////////////////
6309 // URL API
6310 //////////////////////////////////////////////////////////////
6311
6312 var cachedState, lastHistoryState,
6313 lastBrowserUrl = location.href,
6314 baseElement = document.find('base'),
6315 pendingLocation = null,
6316 getCurrentState = !$sniffer.history ? noop : function getCurrentState() {
6317 try {
6318 return history.state;
6319 } catch (e) {
6320 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
6321 }
6322 };
6323
6324 cacheState();
Ed Tanous904063f2017-03-02 16:48:24 -08006325
6326 /**
6327 * @name $browser#url
6328 *
6329 * @description
6330 * GETTER:
6331 * Without any argument, this method just returns current value of location.href.
6332 *
6333 * SETTER:
6334 * With at least one argument, this method sets url to new value.
6335 * If html5 history api supported, pushState/replaceState is used, otherwise
6336 * location.href/location.replace is used.
6337 * Returns its own instance to allow chaining
6338 *
6339 * NOTE: this api is intended for use only by the $location service. Please use the
6340 * {@link ng.$location $location service} to change url.
6341 *
6342 * @param {string} url New url (when used as setter)
6343 * @param {boolean=} replace Should new url replace current history record?
6344 * @param {object=} state object to use with pushState/replaceState
6345 */
6346 self.url = function(url, replace, state) {
6347 // In modern browsers `history.state` is `null` by default; treating it separately
6348 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
6349 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
6350 if (isUndefined(state)) {
6351 state = null;
6352 }
6353
6354 // Android Browser BFCache causes location, history reference to become stale.
6355 if (location !== window.location) location = window.location;
6356 if (history !== window.history) history = window.history;
6357
6358 // setter
6359 if (url) {
6360 var sameState = lastHistoryState === state;
6361
6362 // Don't change anything if previous and current URLs and states match. This also prevents
6363 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
6364 // See https://github.com/angular/angular.js/commit/ffb2701
6365 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
6366 return self;
6367 }
6368 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
6369 lastBrowserUrl = url;
6370 lastHistoryState = state;
6371 // Don't use history API if only the hash changed
6372 // due to a bug in IE10/IE11 which leads
6373 // to not firing a `hashchange` nor `popstate` event
6374 // in some cases (see #9143).
6375 if ($sniffer.history && (!sameBase || !sameState)) {
6376 history[replace ? 'replaceState' : 'pushState'](state, '', url);
6377 cacheState();
Ed Tanous904063f2017-03-02 16:48:24 -08006378 } else {
6379 if (!sameBase) {
6380 pendingLocation = url;
6381 }
6382 if (replace) {
6383 location.replace(url);
6384 } else if (!sameBase) {
6385 location.href = url;
6386 } else {
6387 location.hash = getHash(url);
6388 }
6389 if (location.href !== url) {
6390 pendingLocation = url;
6391 }
6392 }
6393 if (pendingLocation) {
6394 pendingLocation = url;
6395 }
6396 return self;
6397 // getter
6398 } else {
6399 // - pendingLocation is needed as browsers don't allow to read out
6400 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
6401 // https://openradar.appspot.com/22186109).
6402 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
Ed Tanous4758d5b2017-06-06 15:28:13 -07006403 return pendingLocation || location.href.replace(/%27/g,'\'');
Ed Tanous904063f2017-03-02 16:48:24 -08006404 }
6405 };
6406
6407 /**
6408 * @name $browser#state
6409 *
6410 * @description
6411 * This method is a getter.
6412 *
6413 * Return history.state or null if history.state is undefined.
6414 *
6415 * @returns {object} state
6416 */
6417 self.state = function() {
6418 return cachedState;
6419 };
6420
6421 var urlChangeListeners = [],
6422 urlChangeInit = false;
6423
6424 function cacheStateAndFireUrlChange() {
6425 pendingLocation = null;
Ed Tanous4758d5b2017-06-06 15:28:13 -07006426 fireStateOrUrlChange();
Ed Tanous904063f2017-03-02 16:48:24 -08006427 }
6428
6429 // This variable should be used *only* inside the cacheState function.
6430 var lastCachedState = null;
6431 function cacheState() {
6432 // This should be the only place in $browser where `history.state` is read.
6433 cachedState = getCurrentState();
6434 cachedState = isUndefined(cachedState) ? null : cachedState;
6435
6436 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
6437 if (equals(cachedState, lastCachedState)) {
6438 cachedState = lastCachedState;
6439 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07006440
Ed Tanous904063f2017-03-02 16:48:24 -08006441 lastCachedState = cachedState;
Ed Tanous4758d5b2017-06-06 15:28:13 -07006442 lastHistoryState = cachedState;
Ed Tanous904063f2017-03-02 16:48:24 -08006443 }
6444
Ed Tanous4758d5b2017-06-06 15:28:13 -07006445 function fireStateOrUrlChange() {
6446 var prevLastHistoryState = lastHistoryState;
6447 cacheState();
6448
6449 if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
Ed Tanous904063f2017-03-02 16:48:24 -08006450 return;
6451 }
6452
6453 lastBrowserUrl = self.url();
6454 lastHistoryState = cachedState;
6455 forEach(urlChangeListeners, function(listener) {
6456 listener(self.url(), cachedState);
6457 });
6458 }
6459
6460 /**
6461 * @name $browser#onUrlChange
6462 *
6463 * @description
6464 * Register callback function that will be called, when url changes.
6465 *
6466 * It's only called when the url is changed from outside of angular:
6467 * - user types different url into address bar
6468 * - user clicks on history (forward/back) button
6469 * - user clicks on a link
6470 *
6471 * It's not called when url is changed by $browser.url() method
6472 *
6473 * The listener gets called with new url as parameter.
6474 *
6475 * NOTE: this api is intended for use only by the $location service. Please use the
6476 * {@link ng.$location $location service} to monitor url changes in angular apps.
6477 *
6478 * @param {function(string)} listener Listener function to be called when url changes.
6479 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
6480 */
6481 self.onUrlChange = function(callback) {
6482 // TODO(vojta): refactor to use node's syntax for events
6483 if (!urlChangeInit) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006484 // We listen on both (hashchange/popstate) when available, as some browsers don't
6485 // fire popstate when user changes the address bar and don't fire hashchange when url
Ed Tanous904063f2017-03-02 16:48:24 -08006486 // changed by push/replaceState
6487
6488 // html5 history api - popstate event
6489 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
6490 // hashchange event
6491 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
6492
6493 urlChangeInit = true;
6494 }
6495
6496 urlChangeListeners.push(callback);
6497 return callback;
6498 };
6499
6500 /**
6501 * @private
6502 * Remove popstate and hashchange handler from window.
6503 *
6504 * NOTE: this api is intended for use only by $rootScope.
6505 */
6506 self.$$applicationDestroyed = function() {
6507 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
6508 };
6509
6510 /**
6511 * Checks whether the url has changed outside of Angular.
6512 * Needs to be exported to be able to check for changes that have been done in sync,
6513 * as hashchange/popstate events fire in async.
6514 */
Ed Tanous4758d5b2017-06-06 15:28:13 -07006515 self.$$checkUrlChange = fireStateOrUrlChange;
Ed Tanous904063f2017-03-02 16:48:24 -08006516
6517 //////////////////////////////////////////////////////////////
6518 // Misc API
6519 //////////////////////////////////////////////////////////////
6520
6521 /**
6522 * @name $browser#baseHref
6523 *
6524 * @description
6525 * Returns current <base href>
6526 * (always relative - without domain)
6527 *
6528 * @returns {string} The current base href
6529 */
6530 self.baseHref = function() {
6531 var href = baseElement.attr('href');
Ed Tanous4758d5b2017-06-06 15:28:13 -07006532 return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : '';
Ed Tanous904063f2017-03-02 16:48:24 -08006533 };
6534
6535 /**
6536 * @name $browser#defer
6537 * @param {function()} fn A function, who's execution should be deferred.
6538 * @param {number=} [delay=0] of milliseconds to defer the function execution.
6539 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
6540 *
6541 * @description
6542 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
6543 *
6544 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
6545 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
6546 * via `$browser.defer.flush()`.
6547 *
6548 */
6549 self.defer = function(fn, delay) {
6550 var timeoutId;
6551 outstandingRequestCount++;
6552 timeoutId = setTimeout(function() {
6553 delete pendingDeferIds[timeoutId];
6554 completeOutstandingRequest(fn);
6555 }, delay || 0);
6556 pendingDeferIds[timeoutId] = true;
6557 return timeoutId;
6558 };
6559
6560
6561 /**
6562 * @name $browser#defer.cancel
6563 *
6564 * @description
6565 * Cancels a deferred task identified with `deferId`.
6566 *
6567 * @param {*} deferId Token returned by the `$browser.defer` function.
6568 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
6569 * canceled.
6570 */
6571 self.defer.cancel = function(deferId) {
6572 if (pendingDeferIds[deferId]) {
6573 delete pendingDeferIds[deferId];
6574 clearTimeout(deferId);
6575 completeOutstandingRequest(noop);
6576 return true;
6577 }
6578 return false;
6579 };
6580
6581}
6582
Ed Tanous4758d5b2017-06-06 15:28:13 -07006583/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -08006584function $BrowserProvider() {
6585 this.$get = ['$window', '$log', '$sniffer', '$document',
6586 function($window, $log, $sniffer, $document) {
6587 return new Browser($window, $document, $log, $sniffer);
6588 }];
6589}
6590
6591/**
6592 * @ngdoc service
6593 * @name $cacheFactory
Ed Tanous4758d5b2017-06-06 15:28:13 -07006594 * @this
Ed Tanous904063f2017-03-02 16:48:24 -08006595 *
6596 * @description
6597 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
6598 * them.
6599 *
6600 * ```js
6601 *
6602 * var cache = $cacheFactory('cacheId');
6603 * expect($cacheFactory.get('cacheId')).toBe(cache);
6604 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
6605 *
6606 * cache.put("key", "value");
6607 * cache.put("another key", "another value");
6608 *
6609 * // We've specified no options on creation
6610 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
6611 *
6612 * ```
6613 *
6614 *
6615 * @param {string} cacheId Name or id of the newly created cache.
6616 * @param {object=} options Options object that specifies the cache behavior. Properties:
6617 *
6618 * - `{number=}` `capacity` — turns the cache into LRU cache.
6619 *
6620 * @returns {object} Newly created cache object with the following set of methods:
6621 *
6622 * - `{object}` `info()` — Returns id, size, and options of cache.
6623 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
6624 * it.
6625 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
6626 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
6627 * - `{void}` `removeAll()` — Removes all cached values.
6628 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
6629 *
6630 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -07006631 <example module="cacheExampleApp" name="cache-factory">
Ed Tanous904063f2017-03-02 16:48:24 -08006632 <file name="index.html">
6633 <div ng-controller="CacheController">
6634 <input ng-model="newCacheKey" placeholder="Key">
6635 <input ng-model="newCacheValue" placeholder="Value">
6636 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
6637
6638 <p ng-if="keys.length">Cached Values</p>
6639 <div ng-repeat="key in keys">
6640 <span ng-bind="key"></span>
6641 <span>: </span>
6642 <b ng-bind="cache.get(key)"></b>
6643 </div>
6644
6645 <p>Cache Info</p>
6646 <div ng-repeat="(key, value) in cache.info()">
6647 <span ng-bind="key"></span>
6648 <span>: </span>
6649 <b ng-bind="value"></b>
6650 </div>
6651 </div>
6652 </file>
6653 <file name="script.js">
6654 angular.module('cacheExampleApp', []).
6655 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6656 $scope.keys = [];
6657 $scope.cache = $cacheFactory('cacheId');
6658 $scope.put = function(key, value) {
6659 if (angular.isUndefined($scope.cache.get(key))) {
6660 $scope.keys.push(key);
6661 }
6662 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6663 };
6664 }]);
6665 </file>
6666 <file name="style.css">
6667 p {
6668 margin: 10px 0 3px;
6669 }
6670 </file>
6671 </example>
6672 */
6673function $CacheFactoryProvider() {
6674
6675 this.$get = function() {
6676 var caches = {};
6677
6678 function cacheFactory(cacheId, options) {
6679 if (cacheId in caches) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006680 throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId);
Ed Tanous904063f2017-03-02 16:48:24 -08006681 }
6682
6683 var size = 0,
6684 stats = extend({}, options, {id: cacheId}),
6685 data = createMap(),
6686 capacity = (options && options.capacity) || Number.MAX_VALUE,
6687 lruHash = createMap(),
6688 freshEnd = null,
6689 staleEnd = null;
6690
6691 /**
6692 * @ngdoc type
6693 * @name $cacheFactory.Cache
6694 *
6695 * @description
6696 * A cache object used to store and retrieve data, primarily used by
6697 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6698 * templates and other data.
6699 *
6700 * ```js
6701 * angular.module('superCache')
6702 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6703 * return $cacheFactory('super-cache');
6704 * }]);
6705 * ```
6706 *
6707 * Example test:
6708 *
6709 * ```js
6710 * it('should behave like a cache', inject(function(superCache) {
6711 * superCache.put('key', 'value');
6712 * superCache.put('another key', 'another value');
6713 *
6714 * expect(superCache.info()).toEqual({
6715 * id: 'super-cache',
6716 * size: 2
6717 * });
6718 *
6719 * superCache.remove('another key');
6720 * expect(superCache.get('another key')).toBeUndefined();
6721 *
6722 * superCache.removeAll();
6723 * expect(superCache.info()).toEqual({
6724 * id: 'super-cache',
6725 * size: 0
6726 * });
6727 * }));
6728 * ```
6729 */
Ed Tanous4758d5b2017-06-06 15:28:13 -07006730 return (caches[cacheId] = {
Ed Tanous904063f2017-03-02 16:48:24 -08006731
6732 /**
6733 * @ngdoc method
6734 * @name $cacheFactory.Cache#put
6735 * @kind function
6736 *
6737 * @description
6738 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6739 * retrieved later, and incrementing the size of the cache if the key was not already
6740 * present in the cache. If behaving like an LRU cache, it will also remove stale
6741 * entries from the set.
6742 *
6743 * It will not insert undefined values into the cache.
6744 *
6745 * @param {string} key the key under which the cached data is stored.
6746 * @param {*} value the value to store alongside the key. If it is undefined, the key
6747 * will not be stored.
6748 * @returns {*} the value stored.
6749 */
6750 put: function(key, value) {
6751 if (isUndefined(value)) return;
6752 if (capacity < Number.MAX_VALUE) {
6753 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6754
6755 refresh(lruEntry);
6756 }
6757
6758 if (!(key in data)) size++;
6759 data[key] = value;
6760
6761 if (size > capacity) {
6762 this.remove(staleEnd.key);
6763 }
6764
6765 return value;
6766 },
6767
6768 /**
6769 * @ngdoc method
6770 * @name $cacheFactory.Cache#get
6771 * @kind function
6772 *
6773 * @description
6774 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6775 *
6776 * @param {string} key the key of the data to be retrieved
6777 * @returns {*} the value stored.
6778 */
6779 get: function(key) {
6780 if (capacity < Number.MAX_VALUE) {
6781 var lruEntry = lruHash[key];
6782
6783 if (!lruEntry) return;
6784
6785 refresh(lruEntry);
6786 }
6787
6788 return data[key];
6789 },
6790
6791
6792 /**
6793 * @ngdoc method
6794 * @name $cacheFactory.Cache#remove
6795 * @kind function
6796 *
6797 * @description
6798 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6799 *
6800 * @param {string} key the key of the entry to be removed
6801 */
6802 remove: function(key) {
6803 if (capacity < Number.MAX_VALUE) {
6804 var lruEntry = lruHash[key];
6805
6806 if (!lruEntry) return;
6807
Ed Tanous4758d5b2017-06-06 15:28:13 -07006808 if (lruEntry === freshEnd) freshEnd = lruEntry.p;
6809 if (lruEntry === staleEnd) staleEnd = lruEntry.n;
Ed Tanous904063f2017-03-02 16:48:24 -08006810 link(lruEntry.n,lruEntry.p);
6811
6812 delete lruHash[key];
6813 }
6814
6815 if (!(key in data)) return;
6816
6817 delete data[key];
6818 size--;
6819 },
6820
6821
6822 /**
6823 * @ngdoc method
6824 * @name $cacheFactory.Cache#removeAll
6825 * @kind function
6826 *
6827 * @description
6828 * Clears the cache object of any entries.
6829 */
6830 removeAll: function() {
6831 data = createMap();
6832 size = 0;
6833 lruHash = createMap();
6834 freshEnd = staleEnd = null;
6835 },
6836
6837
6838 /**
6839 * @ngdoc method
6840 * @name $cacheFactory.Cache#destroy
6841 * @kind function
6842 *
6843 * @description
6844 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6845 * removing it from the {@link $cacheFactory $cacheFactory} set.
6846 */
6847 destroy: function() {
6848 data = null;
6849 stats = null;
6850 lruHash = null;
6851 delete caches[cacheId];
6852 },
6853
6854
6855 /**
6856 * @ngdoc method
6857 * @name $cacheFactory.Cache#info
6858 * @kind function
6859 *
6860 * @description
6861 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6862 *
6863 * @returns {object} an object with the following properties:
6864 * <ul>
6865 * <li>**id**: the id of the cache instance</li>
6866 * <li>**size**: the number of entries kept in the cache instance</li>
6867 * <li>**...**: any additional properties from the options object when creating the
6868 * cache.</li>
6869 * </ul>
6870 */
6871 info: function() {
6872 return extend({}, stats, {size: size});
6873 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07006874 });
Ed Tanous904063f2017-03-02 16:48:24 -08006875
6876
6877 /**
6878 * makes the `entry` the freshEnd of the LRU linked list
6879 */
6880 function refresh(entry) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006881 if (entry !== freshEnd) {
Ed Tanous904063f2017-03-02 16:48:24 -08006882 if (!staleEnd) {
6883 staleEnd = entry;
Ed Tanous4758d5b2017-06-06 15:28:13 -07006884 } else if (staleEnd === entry) {
Ed Tanous904063f2017-03-02 16:48:24 -08006885 staleEnd = entry.n;
6886 }
6887
6888 link(entry.n, entry.p);
6889 link(entry, freshEnd);
6890 freshEnd = entry;
6891 freshEnd.n = null;
6892 }
6893 }
6894
6895
6896 /**
6897 * bidirectionally links two entries of the LRU linked list
6898 */
6899 function link(nextEntry, prevEntry) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006900 if (nextEntry !== prevEntry) {
Ed Tanous904063f2017-03-02 16:48:24 -08006901 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6902 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6903 }
6904 }
6905 }
6906
6907
6908 /**
6909 * @ngdoc method
6910 * @name $cacheFactory#info
6911 *
6912 * @description
6913 * Get information about all the caches that have been created
6914 *
6915 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6916 */
6917 cacheFactory.info = function() {
6918 var info = {};
6919 forEach(caches, function(cache, cacheId) {
6920 info[cacheId] = cache.info();
6921 });
6922 return info;
6923 };
6924
6925
6926 /**
6927 * @ngdoc method
6928 * @name $cacheFactory#get
6929 *
6930 * @description
6931 * Get access to a cache object by the `cacheId` used when it was created.
6932 *
6933 * @param {string} cacheId Name or id of a cache to access.
6934 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6935 */
6936 cacheFactory.get = function(cacheId) {
6937 return caches[cacheId];
6938 };
6939
6940
6941 return cacheFactory;
6942 };
6943}
6944
6945/**
6946 * @ngdoc service
6947 * @name $templateCache
Ed Tanous4758d5b2017-06-06 15:28:13 -07006948 * @this
Ed Tanous904063f2017-03-02 16:48:24 -08006949 *
6950 * @description
6951 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6952 * can load templates directly into the cache in a `script` tag, or by consuming the
6953 * `$templateCache` service directly.
6954 *
6955 * Adding via the `script` tag:
6956 *
6957 * ```html
6958 * <script type="text/ng-template" id="templateId.html">
6959 * <p>This is the content of the template</p>
6960 * </script>
6961 * ```
6962 *
6963 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6964 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6965 * element with ng-app attribute), otherwise the template will be ignored.
6966 *
6967 * Adding via the `$templateCache` service:
6968 *
6969 * ```js
6970 * var myApp = angular.module('myApp', []);
6971 * myApp.run(function($templateCache) {
6972 * $templateCache.put('templateId.html', 'This is the content of the template');
6973 * });
6974 * ```
6975 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07006976 * To retrieve the template later, simply use it in your component:
6977 * ```js
6978 * myApp.component('myComponent', {
6979 * templateUrl: 'templateId.html'
6980 * });
Ed Tanous904063f2017-03-02 16:48:24 -08006981 * ```
6982 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07006983 * or get it via the `$templateCache` service:
Ed Tanous904063f2017-03-02 16:48:24 -08006984 * ```js
6985 * $templateCache.get('templateId.html')
6986 * ```
6987 *
6988 * See {@link ng.$cacheFactory $cacheFactory}.
6989 *
6990 */
6991function $TemplateCacheProvider() {
6992 this.$get = ['$cacheFactory', function($cacheFactory) {
6993 return $cacheFactory('templates');
6994 }];
6995}
6996
6997/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6998 * Any commits to this file should be reviewed with security in mind. *
6999 * Changes to this file can potentially create security vulnerabilities. *
7000 * An approval from 2 Core members with history of modifying *
7001 * this file is required. *
7002 * *
7003 * Does the change somehow allow for arbitrary javascript to be executed? *
7004 * Or allows for someone to change the prototype of built-in objects? *
Ed Tanous4758d5b2017-06-06 15:28:13 -07007005 * Or gives undesired access to variables like document or window? *
Ed Tanous904063f2017-03-02 16:48:24 -08007006 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
7007
7008/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
7009 *
7010 * DOM-related variables:
7011 *
7012 * - "node" - DOM Node
7013 * - "element" - DOM Element or Node
7014 * - "$node" or "$element" - jqLite-wrapped node or element
7015 *
7016 *
7017 * Compiler related stuff:
7018 *
7019 * - "linkFn" - linking fn of a single directive
7020 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
7021 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
7022 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
7023 */
7024
7025
7026/**
7027 * @ngdoc service
7028 * @name $compile
7029 * @kind function
7030 *
7031 * @description
7032 * Compiles an HTML string or DOM into a template and produces a template function, which
7033 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
7034 *
7035 * The compilation is a process of walking the DOM tree and matching DOM elements to
7036 * {@link ng.$compileProvider#directive directives}.
7037 *
7038 * <div class="alert alert-warning">
7039 * **Note:** This document is an in-depth reference of all directive options.
7040 * For a gentle introduction to directives with examples of common use cases,
7041 * see the {@link guide/directive directive guide}.
7042 * </div>
7043 *
7044 * ## Comprehensive Directive API
7045 *
7046 * There are many different options for a directive.
7047 *
7048 * The difference resides in the return value of the factory function.
7049 * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
7050 * that defines the directive properties, or just the `postLink` function (all other properties will have
7051 * the default values).
7052 *
7053 * <div class="alert alert-success">
7054 * **Best Practice:** It's recommended to use the "directive definition object" form.
7055 * </div>
7056 *
7057 * Here's an example directive declared with a Directive Definition Object:
7058 *
7059 * ```js
7060 * var myModule = angular.module(...);
7061 *
7062 * myModule.directive('directiveName', function factory(injectables) {
7063 * var directiveDefinitionObject = {
Ed Tanous4758d5b2017-06-06 15:28:13 -07007064 * {@link $compile#-priority- priority}: 0,
7065 * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... },
Ed Tanous904063f2017-03-02 16:48:24 -08007066 * // or
Ed Tanous4758d5b2017-06-06 15:28:13 -07007067 * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... },
7068 * {@link $compile#-transclude- transclude}: false,
7069 * {@link $compile#-restrict- restrict}: 'A',
7070 * {@link $compile#-templatenamespace- templateNamespace}: 'html',
7071 * {@link $compile#-scope- scope}: false,
7072 * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
7073 * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier',
7074 * {@link $compile#-bindtocontroller- bindToController}: false,
7075 * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
7076 * {@link $compile#-multielement- multiElement}: false,
7077 * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) {
Ed Tanous904063f2017-03-02 16:48:24 -08007078 * return {
Ed Tanous4758d5b2017-06-06 15:28:13 -07007079 * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7080 * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
Ed Tanous904063f2017-03-02 16:48:24 -08007081 * }
7082 * // or
7083 * // return function postLink( ... ) { ... }
7084 * },
7085 * // or
Ed Tanous4758d5b2017-06-06 15:28:13 -07007086 * // {@link $compile#-link- link}: {
7087 * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7088 * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
Ed Tanous904063f2017-03-02 16:48:24 -08007089 * // }
7090 * // or
Ed Tanous4758d5b2017-06-06 15:28:13 -07007091 * // {@link $compile#-link- link}: function postLink( ... ) { ... }
Ed Tanous904063f2017-03-02 16:48:24 -08007092 * };
7093 * return directiveDefinitionObject;
7094 * });
7095 * ```
7096 *
7097 * <div class="alert alert-warning">
7098 * **Note:** Any unspecified options will use the default value. You can see the default values below.
7099 * </div>
7100 *
7101 * Therefore the above can be simplified as:
7102 *
7103 * ```js
7104 * var myModule = angular.module(...);
7105 *
7106 * myModule.directive('directiveName', function factory(injectables) {
7107 * var directiveDefinitionObject = {
7108 * link: function postLink(scope, iElement, iAttrs) { ... }
7109 * };
7110 * return directiveDefinitionObject;
7111 * // or
7112 * // return function postLink(scope, iElement, iAttrs) { ... }
7113 * });
7114 * ```
7115 *
7116 * ### Life-cycle hooks
7117 * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the
7118 * directive:
7119 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
7120 * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
7121 * this element). This is a good place to put initialization code for your controller.
7122 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
7123 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
7124 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
Ed Tanous4758d5b2017-06-06 15:28:13 -07007125 * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
7126 * also be called when your bindings are initialized.
Ed Tanous904063f2017-03-02 16:48:24 -08007127 * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
7128 * changes. Any actions that you wish to take in response to the changes that you detect must be
7129 * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
7130 * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
7131 * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
7132 * if detecting changes, you must store the previous value(s) for comparison to the current values.
7133 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
7134 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
7135 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
7136 * components will have their `$onDestroy()` hook called before child components.
7137 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
7138 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
7139 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
7140 * they are waiting for their template to load asynchronously and their own compilation and linking has been
7141 * suspended until that occurs.
7142 *
7143 * #### Comparison with Angular 2 life-cycle hooks
7144 * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are
7145 * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2:
7146 *
7147 * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`.
7148 * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor.
7149 * In Angular 2 you can only define hooks on the prototype of the Component class.
7150 * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to
7151 * `ngDoCheck` in Angular 2
7152 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
7153 * propagated throughout the application.
7154 * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
7155 * error or do nothing depending upon the state of `enableProdMode()`.
7156 *
7157 * #### Life-cycle hook examples
7158 *
7159 * This example shows how you can check for mutations to a Date object even though the identity of the object
7160 * has not changed.
7161 *
7162 * <example name="doCheckDateExample" module="do-check-module">
7163 * <file name="app.js">
7164 * angular.module('do-check-module', [])
7165 * .component('app', {
7166 * template:
7167 * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
7168 * 'Date: {{ $ctrl.date }}' +
7169 * '<test date="$ctrl.date"></test>',
7170 * controller: function() {
7171 * this.date = new Date();
7172 * this.month = this.date.getMonth();
7173 * this.updateDate = function() {
7174 * this.date.setMonth(this.month);
7175 * };
7176 * }
7177 * })
7178 * .component('test', {
7179 * bindings: { date: '<' },
7180 * template:
7181 * '<pre>{{ $ctrl.log | json }}</pre>',
7182 * controller: function() {
7183 * var previousValue;
7184 * this.log = [];
7185 * this.$doCheck = function() {
7186 * var currentValue = this.date && this.date.valueOf();
7187 * if (previousValue !== currentValue) {
7188 * this.log.push('doCheck: date mutated: ' + this.date);
7189 * previousValue = currentValue;
7190 * }
7191 * };
7192 * }
7193 * });
7194 * </file>
7195 * <file name="index.html">
7196 * <app></app>
7197 * </file>
7198 * </example>
7199 *
7200 * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
7201 * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
7202 * arrays or objects can have a negative impact on your application performance)
7203 *
7204 * <example name="doCheckArrayExample" module="do-check-module">
7205 * <file name="index.html">
7206 * <div ng-init="items = []">
7207 * <button ng-click="items.push(items.length)">Add Item</button>
7208 * <button ng-click="items = []">Reset Items</button>
7209 * <pre>{{ items }}</pre>
7210 * <test items="items"></test>
7211 * </div>
7212 * </file>
7213 * <file name="app.js">
7214 * angular.module('do-check-module', [])
7215 * .component('test', {
7216 * bindings: { items: '<' },
7217 * template:
7218 * '<pre>{{ $ctrl.log | json }}</pre>',
7219 * controller: function() {
7220 * this.log = [];
7221 *
7222 * this.$doCheck = function() {
7223 * if (this.items_ref !== this.items) {
7224 * this.log.push('doCheck: items changed');
7225 * this.items_ref = this.items;
7226 * }
7227 * if (!angular.equals(this.items_clone, this.items)) {
7228 * this.log.push('doCheck: items mutated');
7229 * this.items_clone = angular.copy(this.items);
7230 * }
7231 * };
7232 * }
7233 * });
7234 * </file>
7235 * </example>
7236 *
7237 *
7238 * ### Directive Definition Object
7239 *
7240 * The directive definition object provides instructions to the {@link ng.$compile
7241 * compiler}. The attributes are:
7242 *
7243 * #### `multiElement`
Ed Tanous4758d5b2017-06-06 15:28:13 -07007244 * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between
Ed Tanous904063f2017-03-02 16:48:24 -08007245 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
7246 * together as the directive elements. It is recommended that this feature be used on directives
7247 * which are not strictly behavioral (such as {@link ngClick}), and which
7248 * do not manipulate or replace child nodes (such as {@link ngInclude}).
7249 *
7250 * #### `priority`
7251 * When there are multiple directives defined on a single DOM element, sometimes it
7252 * is necessary to specify the order in which the directives are applied. The `priority` is used
7253 * to sort the directives before their `compile` functions get called. Priority is defined as a
7254 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
7255 * are also run in priority order, but post-link functions are run in reverse order. The order
7256 * of directives with the same priority is undefined. The default priority is `0`.
7257 *
7258 * #### `terminal`
7259 * If set to true then the current `priority` will be the last set of directives
7260 * which will execute (any directives at the current priority will still execute
7261 * as the order of execution on same `priority` is undefined). Note that expressions
7262 * and other directives used in the directive's template will also be excluded from execution.
7263 *
7264 * #### `scope`
Ed Tanous4758d5b2017-06-06 15:28:13 -07007265 * The scope property can be `false`, `true`, or an object:
Ed Tanous904063f2017-03-02 16:48:24 -08007266 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07007267 * * **`false` (default):** No scope will be created for the directive. The directive will use its
7268 * parent's scope.
Ed Tanous904063f2017-03-02 16:48:24 -08007269 *
7270 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
7271 * the directive's element. If multiple directives on the same element request a new scope,
Ed Tanous4758d5b2017-06-06 15:28:13 -07007272 * only one new scope is created.
Ed Tanous904063f2017-03-02 16:48:24 -08007273 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07007274 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template.
7275 * The 'isolate' scope differs from normal scope in that it does not prototypically
7276 * inherit from its parent scope. This is useful when creating reusable components, which should not
7277 * accidentally read or modify data in the parent scope. Note that an isolate scope
7278 * directive without a `template` or `templateUrl` will not apply the isolate scope
7279 * to its children elements.
Ed Tanous904063f2017-03-02 16:48:24 -08007280 *
7281 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
7282 * directive's element. These local properties are useful for aliasing values for templates. The keys in
7283 * the object hash map to the name of the property on the isolate scope; the values define how the property
7284 * is bound to the parent scope, via matching attributes on the directive's element:
7285 *
7286 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
7287 * always a string since DOM attributes are strings. If no `attr` name is specified then the
7288 * attribute name is assumed to be the same as the local name. Given `<my-component
7289 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
7290 * the directive's scope property `localName` will reflect the interpolated value of `hello
7291 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
7292 * scope. The `name` is read from the parent scope (not the directive's scope).
7293 *
7294 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
7295 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
7296 * If no `attr` name is specified then the attribute name is assumed to be the same as the local
7297 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
7298 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
7299 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
7300 * `localModel` and vice versa. Optional attributes should be marked as such with a question mark:
7301 * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't
7302 * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`})
7303 * will be thrown upon discovering changes to the local value, since it will be impossible to sync
7304 * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
7305 * method is used for tracking changes, and the equality check is based on object identity.
7306 * However, if an object literal or an array literal is passed as the binding expression, the
7307 * equality check is done by value (using the {@link angular.equals} function). It's also possible
7308 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
7309 * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
7310 *
7311 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
7312 * expression passed via the attribute `attr`. The expression is evaluated in the context of the
7313 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
7314 * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
7315 *
7316 * For example, given `<my-component my-attr="parentModel">` and directive definition of
7317 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
7318 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
7319 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
7320 * two caveats:
7321 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
7322 * sets the same value. That means if your bound value is an object, changes to its properties
7323 * in the isolated scope will be reflected in the parent scope (because both reference the same object).
7324 * 2. one-way binding watches changes to the **identity** of the parent value. That means the
7325 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
7326 * to the value has changed. In most cases, this should not be of concern, but can be important
7327 * to know if you one-way bind to an object, and then replace that object in the isolated scope.
7328 * If you now change a property of the object in your parent scope, the change will not be
7329 * propagated to the isolated scope, because the identity of the object on the parent scope
7330 * has not changed. Instead you must assign a new object.
7331 *
7332 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
7333 * back to the parent. However, it does not make this completely impossible.
7334 *
7335 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
7336 * no `attr` name is specified then the attribute name is assumed to be the same as the local name.
7337 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
7338 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
7339 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
7340 * via an expression to the parent scope. This can be done by passing a map of local variable names
7341 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
7342 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
7343 *
7344 * In general it's possible to apply more than one directive to one element, but there might be limitations
7345 * depending on the type of scope required by the directives. The following points will help explain these limitations.
7346 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
7347 *
7348 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
7349 * * **child scope** + **no scope** => Both directives will share one single child scope
7350 * * **child scope** + **child scope** => Both directives will share one single child scope
7351 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
7352 * its parent's scope
7353 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
7354 * be applied to the same element.
7355 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
7356 * cannot be applied to the same element.
7357 *
7358 *
7359 * #### `bindToController`
7360 * This property is used to bind scope properties directly to the controller. It can be either
Ed Tanous4758d5b2017-06-06 15:28:13 -07007361 * `true` or an object hash with the same format as the `scope` property.
Ed Tanous904063f2017-03-02 16:48:24 -08007362 *
7363 * When an isolate scope is used for a directive (see above), `bindToController: true` will
7364 * allow a component to have its properties bound to the controller, rather than to scope.
7365 *
7366 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
7367 * properties. You can access these bindings once they have been initialized by providing a controller method called
7368 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
7369 * initialized.
7370 *
7371 * <div class="alert alert-warning">
Ed Tanous4758d5b2017-06-06 15:28:13 -07007372 * **Deprecation warning:** if `$compileProcvider.preAssignBindingsEnabled(true)` was called, bindings for non-ES6 class
7373 * controllers are bound to `this` before the controller constructor is called but this use is now deprecated. Please
7374 * place initialization code that relies upon bindings inside a `$onInit` method on the controller, instead.
Ed Tanous904063f2017-03-02 16:48:24 -08007375 * </div>
7376 *
7377 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
7378 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used
7379 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
7380 * scope (useful for component directives).
7381 *
7382 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
7383 *
7384 *
7385 * #### `controller`
7386 * Controller constructor function. The controller is instantiated before the
7387 * pre-linking phase and can be accessed by other directives (see
7388 * `require` attribute). This allows the directives to communicate with each other and augment
7389 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
7390 *
7391 * * `$scope` - Current scope associated with the element
7392 * * `$element` - Current element
7393 * * `$attrs` - Current attributes object for the element
7394 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
7395 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
7396 * * `scope`: (optional) override the scope.
7397 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
7398 * * `futureParentElement` (optional):
7399 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
7400 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
7401 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
Ed Tanous4758d5b2017-06-06 15:28:13 -07007402 * and when the `cloneLinkingFn` is passed,
Ed Tanous904063f2017-03-02 16:48:24 -08007403 * as those elements need to created and cloned in a special way when they are defined outside their
7404 * usual containers (e.g. like `<svg>`).
7405 * * See also the `directive.templateNamespace` property.
7406 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
Ed Tanous4758d5b2017-06-06 15:28:13 -07007407 * then the default transclusion is provided.
Ed Tanous904063f2017-03-02 16:48:24 -08007408 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
7409 * `true` if the specified slot contains content (i.e. one or more DOM nodes).
7410 *
7411 * #### `require`
7412 * Require another directive and inject its controller as the fourth argument to the linking function. The
7413 * `require` property can be a string, an array or an object:
7414 * * a **string** containing the name of the directive to pass to the linking function
7415 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
7416 * linking function will be an array of controllers in the same order as the names in the `require` property
7417 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
7418 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
7419 * controllers.
7420 *
7421 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
7422 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
7423 * have been constructed but before `$onInit` is called.
7424 * If the name of the required controller is the same as the local name (the key), the name can be
7425 * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
7426 * See the {@link $compileProvider#component} helper for an example of how this can be used.
7427 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
7428 * raised (unless no link function is specified and the required controllers are not being bound to the directive
7429 * controller, in which case error checking is skipped). The name can be prefixed with:
7430 *
7431 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
7432 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
7433 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
7434 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
7435 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
7436 * `null` to the `link` fn if not found.
7437 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
7438 * `null` to the `link` fn if not found.
7439 *
7440 *
7441 * #### `controllerAs`
7442 * Identifier name for a reference to the controller in the directive's scope.
7443 * This allows the controller to be referenced from the directive template. This is especially
7444 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
7445 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
7446 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
7447 *
7448 *
7449 * #### `restrict`
7450 * String of subset of `EACM` which restricts the directive to a specific directive
7451 * declaration style. If omitted, the defaults (elements and attributes) are used.
7452 *
7453 * * `E` - Element name (default): `<my-directive></my-directive>`
7454 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
7455 * * `C` - Class: `<div class="my-directive: exp;"></div>`
7456 * * `M` - Comment: `<!-- directive: my-directive exp -->`
7457 *
7458 *
7459 * #### `templateNamespace`
7460 * String representing the document type used by the markup in the template.
7461 * AngularJS needs this information as those elements need to be created and cloned
7462 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
7463 *
7464 * * `html` - All root nodes in the template are HTML. Root nodes may also be
7465 * top-level elements such as `<svg>` or `<math>`.
7466 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
7467 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
7468 *
7469 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
7470 *
7471 * #### `template`
7472 * HTML markup that may:
7473 * * Replace the contents of the directive's element (default).
7474 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
7475 * * Wrap the contents of the directive's element (if `transclude` is true).
7476 *
7477 * Value may be:
7478 *
7479 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
7480 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
7481 * function api below) and returns a string value.
7482 *
7483 *
7484 * #### `templateUrl`
7485 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
7486 *
7487 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
7488 * for later when the template has been resolved. In the meantime it will continue to compile and link
7489 * sibling and parent elements as though this element had not contained any directives.
7490 *
7491 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
7492 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
7493 * case when only one deeply nested directive has `templateUrl`.
7494 *
7495 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
7496 *
7497 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
7498 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
7499 * a string value representing the url. In either case, the template URL is passed through {@link
7500 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
7501 *
7502 *
7503 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
7504 * specify what the template should replace. Defaults to `false`.
7505 *
7506 * * `true` - the template will replace the directive's element.
7507 * * `false` - the template will replace the contents of the directive's element.
7508 *
7509 * The replacement process migrates all of the attributes / classes from the old element to the new
7510 * one. See the {@link guide/directive#template-expanding-directive
7511 * Directives Guide} for an example.
7512 *
7513 * There are very few scenarios where element replacement is required for the application function,
7514 * the main one being reusable custom components that are used within SVG contexts
7515 * (because SVG doesn't work with custom elements in the DOM tree).
7516 *
7517 * #### `transclude`
7518 * Extract the contents of the element where the directive appears and make it available to the directive.
7519 * The contents are compiled and provided to the directive as a **transclusion function**. See the
7520 * {@link $compile#transclusion Transclusion} section below.
7521 *
7522 *
7523 * #### `compile`
7524 *
7525 * ```js
7526 * function compile(tElement, tAttrs, transclude) { ... }
7527 * ```
7528 *
7529 * The compile function deals with transforming the template DOM. Since most directives do not do
7530 * template transformation, it is not used often. The compile function takes the following arguments:
7531 *
7532 * * `tElement` - template element - The element where the directive has been declared. It is
7533 * safe to do template transformation on the element and child elements only.
7534 *
7535 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
7536 * between all directive compile functions.
7537 *
7538 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
7539 *
7540 * <div class="alert alert-warning">
7541 * **Note:** The template instance and the link instance may be different objects if the template has
7542 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
7543 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
7544 * should be done in a linking function rather than in a compile function.
7545 * </div>
7546
7547 * <div class="alert alert-warning">
7548 * **Note:** The compile function cannot handle directives that recursively use themselves in their
7549 * own templates or compile functions. Compiling these directives results in an infinite loop and
7550 * stack overflow errors.
7551 *
7552 * This can be avoided by manually using $compile in the postLink function to imperatively compile
7553 * a directive's template instead of relying on automatic template compilation via `template` or
7554 * `templateUrl` declaration or manual compilation inside the compile function.
7555 * </div>
7556 *
7557 * <div class="alert alert-danger">
7558 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
7559 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
7560 * to the link function instead.
7561 * </div>
7562
7563 * A compile function can have a return value which can be either a function or an object.
7564 *
7565 * * returning a (post-link) function - is equivalent to registering the linking function via the
7566 * `link` property of the config object when the compile function is empty.
7567 *
7568 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
7569 * control when a linking function should be called during the linking phase. See info about
7570 * pre-linking and post-linking functions below.
7571 *
7572 *
7573 * #### `link`
7574 * This property is used only if the `compile` property is not defined.
7575 *
7576 * ```js
7577 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
7578 * ```
7579 *
7580 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
7581 * executed after the template has been cloned. This is where most of the directive logic will be
7582 * put.
7583 *
7584 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
7585 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
7586 *
7587 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
7588 * manipulate the children of the element only in `postLink` function since the children have
7589 * already been linked.
7590 *
7591 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
7592 * between all directive linking functions.
7593 *
7594 * * `controller` - the directive's required controller instance(s) - Instances are shared
7595 * among all directives, which allows the directives to use the controllers as a communication
7596 * channel. The exact value depends on the directive's `require` property:
7597 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
7598 * * `string`: the controller instance
7599 * * `array`: array of controller instances
7600 *
7601 * If a required controller cannot be found, and it is optional, the instance is `null`,
7602 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
7603 *
7604 * Note that you can also require the directive's own controller - it will be made available like
7605 * any other controller.
7606 *
7607 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
7608 * This is the same as the `$transclude` parameter of directive controllers,
7609 * see {@link ng.$compile#-controller- the controller section for details}.
7610 * `function([scope], cloneLinkingFn, futureParentElement)`.
7611 *
7612 * #### Pre-linking function
7613 *
7614 * Executed before the child elements are linked. Not safe to do DOM transformation since the
7615 * compiler linking function will fail to locate the correct elements for linking.
7616 *
7617 * #### Post-linking function
7618 *
7619 * Executed after the child elements are linked.
7620 *
7621 * Note that child elements that contain `templateUrl` directives will not have been compiled
7622 * and linked since they are waiting for their template to load asynchronously and their own
7623 * compilation and linking has been suspended until that occurs.
7624 *
7625 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
7626 * for their async templates to be resolved.
7627 *
7628 *
7629 * ### Transclusion
7630 *
7631 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
7632 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
7633 * scope from where they were taken.
7634 *
7635 * Transclusion is used (often with {@link ngTransclude}) to insert the
7636 * original contents of a directive's element into a specified place in the template of the directive.
7637 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
7638 * content has access to the properties on the scope from which it was taken, even if the directive
7639 * has isolated scope.
7640 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
7641 *
7642 * This makes it possible for the widget to have private state for its template, while the transcluded
7643 * content has access to its originating scope.
7644 *
7645 * <div class="alert alert-warning">
7646 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
7647 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
7648 * Testing Transclusion Directives}.
7649 * </div>
7650 *
7651 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
7652 * directive's element, the entire element or multiple parts of the element contents:
7653 *
7654 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
7655 * * `'element'` - transclude the whole of the directive's element including any directives on this
7656 * element that defined at a lower priority than this directive. When used, the `template`
7657 * property is ignored.
7658 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
7659 *
7660 * **Mult-slot transclusion** is declared by providing an object for the `transclude` property.
7661 *
7662 * This object is a map where the keys are the name of the slot to fill and the value is an element selector
7663 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
7664 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
7665 *
7666 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7667 *
7668 * If the element selector is prefixed with a `?` then that slot is optional.
7669 *
7670 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
7671 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
7672 *
7673 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
7674 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
7675 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
7676 * injectable into the directive's controller.
7677 *
7678 *
7679 * #### Transclusion Functions
7680 *
7681 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
7682 * function** to the directive's `link` function and `controller`. This transclusion function is a special
7683 * **linking function** that will return the compiled contents linked to a new transclusion scope.
7684 *
7685 * <div class="alert alert-info">
7686 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
7687 * ngTransclude will deal with it for us.
7688 * </div>
7689 *
7690 * If you want to manually control the insertion and removal of the transcluded content in your directive
7691 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
7692 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
7693 *
7694 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
7695 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
Ed Tanous4758d5b2017-06-06 15:28:13 -07007696 * content and the `scope` is the newly created transclusion scope, which the clone will be linked to.
Ed Tanous904063f2017-03-02 16:48:24 -08007697 *
7698 * <div class="alert alert-info">
7699 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
7700 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
7701 * </div>
7702 *
7703 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
7704 * attach function**:
7705 *
7706 * ```js
7707 * var transcludedContent, transclusionScope;
7708 *
7709 * $transclude(function(clone, scope) {
7710 * element.append(clone);
7711 * transcludedContent = clone;
7712 * transclusionScope = scope;
7713 * });
7714 * ```
7715 *
7716 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
7717 * associated transclusion scope:
7718 *
7719 * ```js
7720 * transcludedContent.remove();
7721 * transclusionScope.$destroy();
7722 * ```
7723 *
7724 * <div class="alert alert-info">
7725 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
7726 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
7727 * then you are also responsible for calling `$destroy` on the transclusion scope.
7728 * </div>
7729 *
7730 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
7731 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
7732 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
7733 *
7734 *
7735 * #### Transclusion Scopes
7736 *
7737 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
7738 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
7739 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
7740 * was taken.
7741 *
7742 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
7743 * like this:
7744 *
7745 * ```html
7746 * <div ng-app>
7747 * <div isolate>
7748 * <div transclusion>
7749 * </div>
7750 * </div>
7751 * </div>
7752 * ```
7753 *
7754 * The `$parent` scope hierarchy will look like this:
7755 *
7756 ```
7757 - $rootScope
7758 - isolate
7759 - transclusion
7760 ```
7761 *
7762 * but the scopes will inherit prototypically from different scopes to their `$parent`.
7763 *
7764 ```
7765 - $rootScope
7766 - transclusion
7767 - isolate
7768 ```
7769 *
7770 *
7771 * ### Attributes
7772 *
7773 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
7774 * `link()` or `compile()` functions. It has a variety of uses.
7775 *
7776 * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways:
7777 * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access
7778 * to the attributes.
7779 *
7780 * * *Directive inter-communication:* All directives share the same instance of the attributes
7781 * object which allows the directives to use the attributes object as inter directive
7782 * communication.
7783 *
7784 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
7785 * allowing other directives to read the interpolated value.
7786 *
7787 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
7788 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
7789 * the only way to easily get the actual value because during the linking phase the interpolation
7790 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
7791 *
7792 * ```js
7793 * function linkingFn(scope, elm, attrs, ctrl) {
7794 * // get the attribute value
7795 * console.log(attrs.ngModel);
7796 *
7797 * // change the attribute
7798 * attrs.$set('ngModel', 'new value');
7799 *
7800 * // observe changes to interpolated attribute
7801 * attrs.$observe('ngModel', function(value) {
7802 * console.log('ngModel has changed value to ' + value);
7803 * });
7804 * }
7805 * ```
7806 *
7807 * ## Example
7808 *
7809 * <div class="alert alert-warning">
7810 * **Note**: Typically directives are registered with `module.directive`. The example below is
7811 * to illustrate how `$compile` works.
7812 * </div>
7813 *
Ed Tanous4758d5b2017-06-06 15:28:13 -07007814 <example module="compileExample" name="compile">
Ed Tanous904063f2017-03-02 16:48:24 -08007815 <file name="index.html">
7816 <script>
7817 angular.module('compileExample', [], function($compileProvider) {
7818 // configure new 'compile' directive by passing a directive
7819 // factory function. The factory function injects the '$compile'
7820 $compileProvider.directive('compile', function($compile) {
7821 // directive factory creates a link function
7822 return function(scope, element, attrs) {
7823 scope.$watch(
7824 function(scope) {
7825 // watch the 'compile' expression for changes
7826 return scope.$eval(attrs.compile);
7827 },
7828 function(value) {
7829 // when the 'compile' expression changes
7830 // assign it into the current DOM
7831 element.html(value);
7832
7833 // compile the new DOM and link it to the current
7834 // scope.
7835 // NOTE: we only compile .childNodes so that
7836 // we don't get into infinite loop compiling ourselves
7837 $compile(element.contents())(scope);
7838 }
7839 );
7840 };
7841 });
7842 })
7843 .controller('GreeterController', ['$scope', function($scope) {
7844 $scope.name = 'Angular';
7845 $scope.html = 'Hello {{name}}';
7846 }]);
7847 </script>
7848 <div ng-controller="GreeterController">
7849 <input ng-model="name"> <br/>
7850 <textarea ng-model="html"></textarea> <br/>
7851 <div compile="html"></div>
7852 </div>
7853 </file>
7854 <file name="protractor.js" type="protractor">
7855 it('should auto compile', function() {
7856 var textarea = $('textarea');
7857 var output = $('div[compile]');
7858 // The initial state reads 'Hello Angular'.
7859 expect(output.getText()).toBe('Hello Angular');
7860 textarea.clear();
7861 textarea.sendKeys('{{name}}!');
7862 expect(output.getText()).toBe('Angular!');
7863 });
7864 </file>
7865 </example>
7866
7867 *
7868 *
7869 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7870 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7871 *
7872 * <div class="alert alert-danger">
7873 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7874 * e.g. will not use the right outer scope. Please pass the transclude function as a
7875 * `parentBoundTranscludeFn` to the link function instead.
7876 * </div>
7877 *
7878 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7879 * root element(s), not their children)
7880 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7881 * (a DOM element/tree) to a scope. Where:
7882 *
7883 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7884 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7885 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7886 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7887 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7888 *
7889 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7890 * * `scope` - is the current scope with which the linking function is working with.
7891 *
7892 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7893 * keys may be used to control linking behavior:
7894 *
7895 * * `parentBoundTranscludeFn` - the transclude function made available to
7896 * directives; if given, it will be passed through to the link functions of
7897 * directives found in `element` during compilation.
7898 * * `transcludeControllers` - an object hash with keys that map controller names
7899 * to a hash with the key `instance`, which maps to the controller instance;
7900 * if given, it will make the controllers available to directives on the compileNode:
7901 * ```
7902 * {
7903 * parent: {
7904 * instance: parentControllerInstance
7905 * }
7906 * }
7907 * ```
7908 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7909 * the cloned elements; only needed for transcludes that are allowed to contain non html
7910 * elements (e.g. SVG elements). See also the directive.controller property.
7911 *
7912 * Calling the linking function returns the element of the template. It is either the original
7913 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7914 *
7915 * After linking the view is not updated until after a call to $digest which typically is done by
7916 * Angular automatically.
7917 *
7918 * If you need access to the bound view, there are two ways to do it:
7919 *
7920 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7921 * before you send them to the compiler and keep this reference around.
7922 * ```js
7923 * var element = $compile('<p>{{total}}</p>')(scope);
7924 * ```
7925 *
7926 * - if on the other hand, you need the element to be cloned, the view reference from the original
7927 * example would not point to the clone, but rather to the original template that was cloned. In
7928 * this case, you can access the clone via the cloneAttachFn:
7929 * ```js
7930 * var templateElement = angular.element('<p>{{total}}</p>'),
7931 * scope = ....;
7932 *
7933 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7934 * //attach the clone to DOM document at the right place
7935 * });
7936 *
7937 * //now we have reference to the cloned DOM via `clonedElement`
7938 * ```
7939 *
7940 *
7941 * For information on how the compiler works, see the
7942 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
Ed Tanous4758d5b2017-06-06 15:28:13 -07007943 *
7944 * @knownIssue
7945 *
7946 * ### Double Compilation
7947 *
7948 Double compilation occurs when an already compiled part of the DOM gets
7949 compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
7950 and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
7951 section on double compilation} for an in-depth explanation and ways to avoid it.
7952 *
Ed Tanous904063f2017-03-02 16:48:24 -08007953 */
7954
7955var $compileMinErr = minErr('$compile');
7956
7957function UNINITIALIZED_VALUE() {}
7958var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
7959
7960/**
7961 * @ngdoc provider
7962 * @name $compileProvider
7963 *
7964 * @description
7965 */
7966$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
Ed Tanous4758d5b2017-06-06 15:28:13 -07007967/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -08007968function $CompileProvider($provide, $$sanitizeUriProvider) {
7969 var hasDirectives = {},
7970 Suffix = 'Directive',
Ed Tanous4758d5b2017-06-06 15:28:13 -07007971 COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/,
7972 CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/,
Ed Tanous904063f2017-03-02 16:48:24 -08007973 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7974 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7975
7976 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7977 // The assumption is that future DOM event attribute names will begin with
7978 // 'on' and be composed of only English letters.
7979 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7980 var bindingCache = createMap();
7981
7982 function parseIsolateBindings(scope, directiveName, isController) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07007983 var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/;
Ed Tanous904063f2017-03-02 16:48:24 -08007984
7985 var bindings = createMap();
7986
7987 forEach(scope, function(definition, scopeName) {
7988 if (definition in bindingCache) {
7989 bindings[scopeName] = bindingCache[definition];
7990 return;
7991 }
7992 var match = definition.match(LOCAL_REGEXP);
7993
7994 if (!match) {
7995 throw $compileMinErr('iscp',
Ed Tanous4758d5b2017-06-06 15:28:13 -07007996 'Invalid {3} for directive \'{0}\'.' +
7997 ' Definition: {... {1}: \'{2}\' ...}',
Ed Tanous904063f2017-03-02 16:48:24 -08007998 directiveName, scopeName, definition,
Ed Tanous4758d5b2017-06-06 15:28:13 -07007999 (isController ? 'controller bindings definition' :
8000 'isolate scope definition'));
Ed Tanous904063f2017-03-02 16:48:24 -08008001 }
8002
8003 bindings[scopeName] = {
8004 mode: match[1][0],
8005 collection: match[2] === '*',
8006 optional: match[3] === '?',
8007 attrName: match[4] || scopeName
8008 };
8009 if (match[4]) {
8010 bindingCache[definition] = bindings[scopeName];
8011 }
8012 });
8013
8014 return bindings;
8015 }
8016
8017 function parseDirectiveBindings(directive, directiveName) {
8018 var bindings = {
8019 isolateScope: null,
8020 bindToController: null
8021 };
8022 if (isObject(directive.scope)) {
8023 if (directive.bindToController === true) {
8024 bindings.bindToController = parseIsolateBindings(directive.scope,
8025 directiveName, true);
8026 bindings.isolateScope = {};
8027 } else {
8028 bindings.isolateScope = parseIsolateBindings(directive.scope,
8029 directiveName, false);
8030 }
8031 }
8032 if (isObject(directive.bindToController)) {
8033 bindings.bindToController =
8034 parseIsolateBindings(directive.bindToController, directiveName, true);
8035 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07008036 if (bindings.bindToController && !directive.controller) {
8037 // There is no controller
8038 throw $compileMinErr('noctrl',
8039 'Cannot bind to controller without directive \'{0}\'s controller.',
8040 directiveName);
Ed Tanous904063f2017-03-02 16:48:24 -08008041 }
8042 return bindings;
8043 }
8044
8045 function assertValidDirectiveName(name) {
8046 var letter = name.charAt(0);
8047 if (!letter || letter !== lowercase(letter)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07008048 throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name);
Ed Tanous904063f2017-03-02 16:48:24 -08008049 }
8050 if (name !== name.trim()) {
8051 throw $compileMinErr('baddir',
Ed Tanous4758d5b2017-06-06 15:28:13 -07008052 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces',
Ed Tanous904063f2017-03-02 16:48:24 -08008053 name);
8054 }
8055 }
8056
8057 function getDirectiveRequire(directive) {
8058 var require = directive.require || (directive.controller && directive.name);
8059
8060 if (!isArray(require) && isObject(require)) {
8061 forEach(require, function(value, key) {
8062 var match = value.match(REQUIRE_PREFIX_REGEXP);
8063 var name = value.substring(match[0].length);
8064 if (!name) require[key] = match[0] + key;
8065 });
8066 }
8067
8068 return require;
8069 }
8070
Ed Tanous4758d5b2017-06-06 15:28:13 -07008071 function getDirectiveRestrict(restrict, name) {
8072 if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) {
8073 throw $compileMinErr('badrestrict',
8074 'Restrict property \'{0}\' of directive \'{1}\' is invalid',
8075 restrict,
8076 name);
8077 }
8078
8079 return restrict || 'EA';
8080 }
8081
Ed Tanous904063f2017-03-02 16:48:24 -08008082 /**
8083 * @ngdoc method
8084 * @name $compileProvider#directive
8085 * @kind function
8086 *
8087 * @description
8088 * Register a new directive with the compiler.
8089 *
8090 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
8091 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
8092 * names and the values are the factories.
8093 * @param {Function|Array} directiveFactory An injectable directive factory function. See the
8094 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
8095 * @returns {ng.$compileProvider} Self for chaining.
8096 */
8097 this.directive = function registerDirective(name, directiveFactory) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07008098 assertArg(name, 'name');
Ed Tanous904063f2017-03-02 16:48:24 -08008099 assertNotHasOwnProperty(name, 'directive');
8100 if (isString(name)) {
8101 assertValidDirectiveName(name);
8102 assertArg(directiveFactory, 'directiveFactory');
8103 if (!hasDirectives.hasOwnProperty(name)) {
8104 hasDirectives[name] = [];
8105 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
8106 function($injector, $exceptionHandler) {
8107 var directives = [];
8108 forEach(hasDirectives[name], function(directiveFactory, index) {
8109 try {
8110 var directive = $injector.invoke(directiveFactory);
8111 if (isFunction(directive)) {
8112 directive = { compile: valueFn(directive) };
8113 } else if (!directive.compile && directive.link) {
8114 directive.compile = valueFn(directive.link);
8115 }
8116 directive.priority = directive.priority || 0;
8117 directive.index = index;
8118 directive.name = directive.name || name;
8119 directive.require = getDirectiveRequire(directive);
Ed Tanous4758d5b2017-06-06 15:28:13 -07008120 directive.restrict = getDirectiveRestrict(directive.restrict, name);
Ed Tanous904063f2017-03-02 16:48:24 -08008121 directive.$$moduleName = directiveFactory.$$moduleName;
8122 directives.push(directive);
8123 } catch (e) {
8124 $exceptionHandler(e);
8125 }
8126 });
8127 return directives;
8128 }]);
8129 }
8130 hasDirectives[name].push(directiveFactory);
8131 } else {
8132 forEach(name, reverseParams(registerDirective));
8133 }
8134 return this;
8135 };
8136
8137 /**
8138 * @ngdoc method
8139 * @name $compileProvider#component
8140 * @module ng
8141 * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`)
8142 * @param {Object} options Component definition object (a simplified
8143 * {@link ng.$compile#directive-definition-object directive definition object}),
8144 * with the following properties (all optional):
8145 *
8146 * - `controller` – `{(string|function()=}` – controller constructor function that should be
8147 * associated with newly created scope or the name of a {@link ng.$compile#-controller-
8148 * registered controller} if passed as a string. An empty `noop` function by default.
8149 * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
8150 * If present, the controller will be published to scope under the `controllerAs` name.
8151 * If not present, this will default to be `$ctrl`.
8152 * - `template` – `{string=|function()=}` – html template as a string or a function that
8153 * returns an html template as a string which should be used as the contents of this component.
8154 * Empty string by default.
8155 *
8156 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with
8157 * the following locals:
8158 *
8159 * - `$element` - Current element
8160 * - `$attrs` - Current attributes object for the element
8161 *
8162 * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
8163 * template that should be used as the contents of this component.
8164 *
8165 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
8166 * the following locals:
8167 *
8168 * - `$element` - Current element
8169 * - `$attrs` - Current attributes object for the element
8170 *
8171 * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
8172 * Component properties are always bound to the component controller and not to the scope.
8173 * See {@link ng.$compile#-bindtocontroller- `bindToController`}.
8174 * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
8175 * Disabled by default.
8176 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to
8177 * this component's controller. The object keys specify the property names under which the required
8178 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}.
8179 * - `$...` – additional properties to attach to the directive factory function and the controller
8180 * constructor function. (This is used by the component router to annotate)
8181 *
8182 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
8183 * @description
8184 * Register a **component definition** with the compiler. This is a shorthand for registering a special
8185 * type of directive, which represents a self-contained UI component in your application. Such components
8186 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
8187 *
8188 * Component definitions are very simple and do not require as much configuration as defining general
8189 * directives. Component definitions usually consist only of a template and a controller backing it.
8190 *
8191 * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
8192 * `bindToController`. They always have **isolate scope** and are restricted to elements.
8193 *
8194 * Here are a few examples of how you would usually define components:
8195 *
8196 * ```js
8197 * var myMod = angular.module(...);
8198 * myMod.component('myComp', {
8199 * template: '<div>My name is {{$ctrl.name}}</div>',
8200 * controller: function() {
8201 * this.name = 'shahar';
8202 * }
8203 * });
8204 *
8205 * myMod.component('myComp', {
8206 * template: '<div>My name is {{$ctrl.name}}</div>',
8207 * bindings: {name: '@'}
8208 * });
8209 *
8210 * myMod.component('myComp', {
8211 * templateUrl: 'views/my-comp.html',
8212 * controller: 'MyCtrl',
8213 * controllerAs: 'ctrl',
8214 * bindings: {name: '@'}
8215 * });
8216 *
8217 * ```
8218 * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
8219 *
8220 * <br />
8221 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
8222 */
8223 this.component = function registerComponent(name, options) {
8224 var controller = options.controller || function() {};
8225
8226 function factory($injector) {
8227 function makeInjectable(fn) {
8228 if (isFunction(fn) || isArray(fn)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07008229 return /** @this */ function(tElement, tAttrs) {
Ed Tanous904063f2017-03-02 16:48:24 -08008230 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
8231 };
8232 } else {
8233 return fn;
8234 }
8235 }
8236
8237 var template = (!options.template && !options.templateUrl ? '' : options.template);
8238 var ddo = {
8239 controller: controller,
8240 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
8241 template: makeInjectable(template),
8242 templateUrl: makeInjectable(options.templateUrl),
8243 transclude: options.transclude,
8244 scope: {},
8245 bindToController: options.bindings || {},
8246 restrict: 'E',
8247 require: options.require
8248 };
8249
8250 // Copy annotations (starting with $) over to the DDO
8251 forEach(options, function(val, key) {
8252 if (key.charAt(0) === '$') ddo[key] = val;
8253 });
8254
8255 return ddo;
8256 }
8257
8258 // TODO(pete) remove the following `forEach` before we release 1.6.0
8259 // The component-router@0.2.0 looks for the annotations on the controller constructor
8260 // Nothing in Angular looks for annotations on the factory function but we can't remove
8261 // it from 1.5.x yet.
8262
8263 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
8264 // These could be used by libraries such as the new component router
8265 forEach(options, function(val, key) {
8266 if (key.charAt(0) === '$') {
8267 factory[key] = val;
8268 // Don't try to copy over annotations to named controller
8269 if (isFunction(controller)) controller[key] = val;
8270 }
8271 });
8272
8273 factory.$inject = ['$injector'];
8274
8275 return this.directive(name, factory);
8276 };
8277
8278
8279 /**
8280 * @ngdoc method
8281 * @name $compileProvider#aHrefSanitizationWhitelist
8282 * @kind function
8283 *
8284 * @description
8285 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
8286 * urls during a[href] sanitization.
8287 *
8288 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
8289 *
8290 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
8291 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
8292 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
8293 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
8294 *
8295 * @param {RegExp=} regexp New regexp to whitelist urls with.
8296 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
8297 * chaining otherwise.
8298 */
8299 this.aHrefSanitizationWhitelist = function(regexp) {
8300 if (isDefined(regexp)) {
8301 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
8302 return this;
8303 } else {
8304 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
8305 }
8306 };
8307
8308
8309 /**
8310 * @ngdoc method
8311 * @name $compileProvider#imgSrcSanitizationWhitelist
8312 * @kind function
8313 *
8314 * @description
8315 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
8316 * urls during img[src] sanitization.
8317 *
8318 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
8319 *
8320 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
8321 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
8322 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
8323 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
8324 *
8325 * @param {RegExp=} regexp New regexp to whitelist urls with.
8326 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
8327 * chaining otherwise.
8328 */
8329 this.imgSrcSanitizationWhitelist = function(regexp) {
8330 if (isDefined(regexp)) {
8331 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
8332 return this;
8333 } else {
8334 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
8335 }
8336 };
8337
8338 /**
8339 * @ngdoc method
8340 * @name $compileProvider#debugInfoEnabled
8341 *
8342 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
8343 * current debugInfoEnabled state
8344 * @returns {*} current value if used as getter or itself (chaining) if used as setter
8345 *
8346 * @kind function
8347 *
8348 * @description
8349 * Call this method to enable/disable various debug runtime information in the compiler such as adding
8350 * binding information and a reference to the current scope on to DOM elements.
8351 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
8352 * * `ng-binding` CSS class
8353 * * `$binding` data property containing an array of the binding expressions
8354 *
8355 * You may want to disable this in production for a significant performance boost. See
8356 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
8357 *
8358 * The default value is true.
8359 */
8360 var debugInfoEnabled = true;
8361 this.debugInfoEnabled = function(enabled) {
8362 if (isDefined(enabled)) {
8363 debugInfoEnabled = enabled;
8364 return this;
8365 }
8366 return debugInfoEnabled;
8367 };
8368
Ed Tanous4758d5b2017-06-06 15:28:13 -07008369 /**
8370 * @ngdoc method
8371 * @name $compileProvider#preAssignBindingsEnabled
8372 *
8373 * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the
8374 * current preAssignBindingsEnabled state
8375 * @returns {*} current value if used as getter or itself (chaining) if used as setter
8376 *
8377 * @kind function
8378 *
8379 * @description
8380 * Call this method to enable/disable whether directive controllers are assigned bindings before
8381 * calling the controller's constructor.
8382 * If enabled (true), the compiler assigns the value of each of the bindings to the
8383 * properties of the controller object before the constructor of this object is called.
8384 *
8385 * If disabled (false), the compiler calls the constructor first before assigning bindings.
8386 *
8387 * The default value is false.
8388 *
8389 * @deprecated
8390 * sinceVersion="1.6.0"
8391 * removeVersion="1.7.0"
8392 *
8393 * This method and the option to assign the bindings before calling the controller's constructor
8394 * will be removed in v1.7.0.
8395 */
8396 var preAssignBindingsEnabled = false;
8397 this.preAssignBindingsEnabled = function(enabled) {
8398 if (isDefined(enabled)) {
8399 preAssignBindingsEnabled = enabled;
8400 return this;
8401 }
8402 return preAssignBindingsEnabled;
8403 };
8404
Ed Tanous904063f2017-03-02 16:48:24 -08008405
8406 var TTL = 10;
8407 /**
8408 * @ngdoc method
8409 * @name $compileProvider#onChangesTtl
8410 * @description
8411 *
8412 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
8413 * assuming that the model is unstable.
8414 *
8415 * The current default is 10 iterations.
8416 *
8417 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
8418 * in several iterations of calls to these hooks. However if an application needs more than the default 10
8419 * iterations to stabilize then you should investigate what is causing the model to continuously change during
8420 * the `$onChanges` hook execution.
8421 *
8422 * Increasing the TTL could have performance implications, so you should not change it without proper justification.
8423 *
8424 * @param {number} limit The number of `$onChanges` hook iterations.
8425 * @returns {number|object} the current limit (or `this` if called as a setter for chaining)
8426 */
8427 this.onChangesTtl = function(value) {
8428 if (arguments.length) {
8429 TTL = value;
8430 return this;
8431 }
8432 return TTL;
8433 };
8434
Ed Tanous4758d5b2017-06-06 15:28:13 -07008435 var commentDirectivesEnabledConfig = true;
8436 /**
8437 * @ngdoc method
8438 * @name $compileProvider#commentDirectivesEnabled
8439 * @description
8440 *
8441 * It indicates to the compiler
8442 * whether or not directives on comments should be compiled.
8443 * Defaults to `true`.
8444 *
8445 * Calling this function with false disables the compilation of directives
8446 * on comments for the whole application.
8447 * This results in a compilation performance gain,
8448 * as the compiler doesn't have to check comments when looking for directives.
8449 * This should however only be used if you are sure that no comment directives are used in
8450 * the application (including any 3rd party directives).
8451 *
8452 * @param {boolean} enabled `false` if the compiler may ignore directives on comments
8453 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
8454 */
8455 this.commentDirectivesEnabled = function(value) {
8456 if (arguments.length) {
8457 commentDirectivesEnabledConfig = value;
8458 return this;
8459 }
8460 return commentDirectivesEnabledConfig;
8461 };
8462
8463
8464 var cssClassDirectivesEnabledConfig = true;
8465 /**
8466 * @ngdoc method
8467 * @name $compileProvider#cssClassDirectivesEnabled
8468 * @description
8469 *
8470 * It indicates to the compiler
8471 * whether or not directives on element classes should be compiled.
8472 * Defaults to `true`.
8473 *
8474 * Calling this function with false disables the compilation of directives
8475 * on element classes for the whole application.
8476 * This results in a compilation performance gain,
8477 * as the compiler doesn't have to check element classes when looking for directives.
8478 * This should however only be used if you are sure that no class directives are used in
8479 * the application (including any 3rd party directives).
8480 *
8481 * @param {boolean} enabled `false` if the compiler may ignore directives on element classes
8482 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
8483 */
8484 this.cssClassDirectivesEnabled = function(value) {
8485 if (arguments.length) {
8486 cssClassDirectivesEnabledConfig = value;
8487 return this;
8488 }
8489 return cssClassDirectivesEnabledConfig;
8490 };
8491
Ed Tanous904063f2017-03-02 16:48:24 -08008492 this.$get = [
8493 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
8494 '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
8495 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
8496 $controller, $rootScope, $sce, $animate, $$sanitizeUri) {
8497
8498 var SIMPLE_ATTR_NAME = /^\w/;
8499 var specialAttrHolder = window.document.createElement('div');
8500
8501
Ed Tanous4758d5b2017-06-06 15:28:13 -07008502 var commentDirectivesEnabled = commentDirectivesEnabledConfig;
8503 var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig;
8504
Ed Tanous904063f2017-03-02 16:48:24 -08008505
8506 var onChangesTtl = TTL;
8507 // The onChanges hooks should all be run together in a single digest
8508 // When changes occur, the call to trigger their hooks will be added to this queue
8509 var onChangesQueue;
8510
8511 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
8512 function flushOnChangesQueue() {
8513 try {
8514 if (!(--onChangesTtl)) {
8515 // We have hit the TTL limit so reset everything
8516 onChangesQueue = undefined;
8517 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
8518 }
8519 // We must run this hook in an apply since the $$postDigest runs outside apply
8520 $rootScope.$apply(function() {
8521 var errors = [];
8522 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
8523 try {
8524 onChangesQueue[i]();
8525 } catch (e) {
8526 errors.push(e);
8527 }
8528 }
8529 // Reset the queue to trigger a new schedule next time there is a change
8530 onChangesQueue = undefined;
8531 if (errors.length) {
8532 throw errors;
8533 }
8534 });
8535 } finally {
8536 onChangesTtl++;
8537 }
8538 }
8539
8540
8541 function Attributes(element, attributesToCopy) {
8542 if (attributesToCopy) {
8543 var keys = Object.keys(attributesToCopy);
8544 var i, l, key;
8545
8546 for (i = 0, l = keys.length; i < l; i++) {
8547 key = keys[i];
8548 this[key] = attributesToCopy[key];
8549 }
8550 } else {
8551 this.$attr = {};
8552 }
8553
8554 this.$$element = element;
8555 }
8556
8557 Attributes.prototype = {
8558 /**
8559 * @ngdoc method
8560 * @name $compile.directive.Attributes#$normalize
8561 * @kind function
8562 *
8563 * @description
8564 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
8565 * `data-`) to its normalized, camelCase form.
8566 *
8567 * Also there is special case for Moz prefix starting with upper case letter.
8568 *
8569 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
8570 *
8571 * @param {string} name Name to normalize
8572 */
8573 $normalize: directiveNormalize,
8574
8575
8576 /**
8577 * @ngdoc method
8578 * @name $compile.directive.Attributes#$addClass
8579 * @kind function
8580 *
8581 * @description
8582 * Adds the CSS class value specified by the classVal parameter to the element. If animations
8583 * are enabled then an animation will be triggered for the class addition.
8584 *
8585 * @param {string} classVal The className value that will be added to the element
8586 */
8587 $addClass: function(classVal) {
8588 if (classVal && classVal.length > 0) {
8589 $animate.addClass(this.$$element, classVal);
8590 }
8591 },
8592
8593 /**
8594 * @ngdoc method
8595 * @name $compile.directive.Attributes#$removeClass
8596 * @kind function
8597 *
8598 * @description
8599 * Removes the CSS class value specified by the classVal parameter from the element. If
8600 * animations are enabled then an animation will be triggered for the class removal.
8601 *
8602 * @param {string} classVal The className value that will be removed from the element
8603 */
8604 $removeClass: function(classVal) {
8605 if (classVal && classVal.length > 0) {
8606 $animate.removeClass(this.$$element, classVal);
8607 }
8608 },
8609
8610 /**
8611 * @ngdoc method
8612 * @name $compile.directive.Attributes#$updateClass
8613 * @kind function
8614 *
8615 * @description
8616 * Adds and removes the appropriate CSS class values to the element based on the difference
8617 * between the new and old CSS class values (specified as newClasses and oldClasses).
8618 *
8619 * @param {string} newClasses The current CSS className value
8620 * @param {string} oldClasses The former CSS className value
8621 */
8622 $updateClass: function(newClasses, oldClasses) {
8623 var toAdd = tokenDifference(newClasses, oldClasses);
8624 if (toAdd && toAdd.length) {
8625 $animate.addClass(this.$$element, toAdd);
8626 }
8627
8628 var toRemove = tokenDifference(oldClasses, newClasses);
8629 if (toRemove && toRemove.length) {
8630 $animate.removeClass(this.$$element, toRemove);
8631 }
8632 },
8633
8634 /**
8635 * Set a normalized attribute on the element in a way such that all directives
8636 * can share the attribute. This function properly handles boolean attributes.
8637 * @param {string} key Normalized key. (ie ngAttribute)
8638 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
8639 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
8640 * Defaults to true.
8641 * @param {string=} attrName Optional none normalized name. Defaults to key.
8642 */
8643 $set: function(key, value, writeAttr, attrName) {
8644 // TODO: decide whether or not to throw an error if "class"
8645 //is set through this function since it may cause $updateClass to
8646 //become unstable.
8647
8648 var node = this.$$element[0],
8649 booleanKey = getBooleanAttrName(node, key),
8650 aliasedKey = getAliasedAttrName(key),
8651 observer = key,
8652 nodeName;
8653
8654 if (booleanKey) {
8655 this.$$element.prop(key, value);
8656 attrName = booleanKey;
8657 } else if (aliasedKey) {
8658 this[aliasedKey] = value;
8659 observer = aliasedKey;
8660 }
8661
8662 this[key] = value;
8663
8664 // translate normalized key to actual key
8665 if (attrName) {
8666 this.$attr[key] = attrName;
8667 } else {
8668 attrName = this.$attr[key];
8669 if (!attrName) {
8670 this.$attr[key] = attrName = snake_case(key, '-');
8671 }
8672 }
8673
8674 nodeName = nodeName_(this.$$element);
8675
8676 if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
8677 (nodeName === 'img' && key === 'src')) {
8678 // sanitize a[href] and img[src] values
8679 this[key] = value = $$sanitizeUri(value, key === 'src');
8680 } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
8681 // sanitize img[srcset] values
Ed Tanous4758d5b2017-06-06 15:28:13 -07008682 var result = '';
Ed Tanous904063f2017-03-02 16:48:24 -08008683
8684 // first check if there are spaces because it's not the same pattern
8685 var trimmedSrcset = trim(value);
8686 // ( 999x ,| 999w ,| ,|, )
8687 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
8688 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
8689
8690 // split srcset into tuple of uri and descriptor except for the last item
8691 var rawUris = trimmedSrcset.split(pattern);
8692
8693 // for each tuples
8694 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
8695 for (var i = 0; i < nbrUrisWith2parts; i++) {
8696 var innerIdx = i * 2;
8697 // sanitize the uri
8698 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
8699 // add the descriptor
Ed Tanous4758d5b2017-06-06 15:28:13 -07008700 result += (' ' + trim(rawUris[innerIdx + 1]));
Ed Tanous904063f2017-03-02 16:48:24 -08008701 }
8702
8703 // split the last item into uri and descriptor
8704 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
8705
8706 // sanitize the last uri
8707 result += $$sanitizeUri(trim(lastTuple[0]), true);
8708
8709 // and add the last descriptor if any
8710 if (lastTuple.length === 2) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07008711 result += (' ' + trim(lastTuple[1]));
Ed Tanous904063f2017-03-02 16:48:24 -08008712 }
8713 this[key] = value = result;
8714 }
8715
8716 if (writeAttr !== false) {
8717 if (value === null || isUndefined(value)) {
8718 this.$$element.removeAttr(attrName);
8719 } else {
8720 if (SIMPLE_ATTR_NAME.test(attrName)) {
8721 this.$$element.attr(attrName, value);
8722 } else {
8723 setSpecialAttr(this.$$element[0], attrName, value);
8724 }
8725 }
8726 }
8727
8728 // fire observers
8729 var $$observers = this.$$observers;
Ed Tanous4758d5b2017-06-06 15:28:13 -07008730 if ($$observers) {
8731 forEach($$observers[observer], function(fn) {
8732 try {
8733 fn(value);
8734 } catch (e) {
8735 $exceptionHandler(e);
8736 }
8737 });
8738 }
Ed Tanous904063f2017-03-02 16:48:24 -08008739 },
8740
8741
8742 /**
8743 * @ngdoc method
8744 * @name $compile.directive.Attributes#$observe
8745 * @kind function
8746 *
8747 * @description
8748 * Observes an interpolated attribute.
8749 *
8750 * The observer function will be invoked once during the next `$digest` following
8751 * compilation. The observer is then invoked whenever the interpolated value
8752 * changes.
8753 *
8754 * @param {string} key Normalized key. (ie ngAttribute) .
8755 * @param {function(interpolatedValue)} fn Function that will be called whenever
8756 the interpolated value of the attribute changes.
8757 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
8758 * guide} for more info.
8759 * @returns {function()} Returns a deregistration function for this observer.
8760 */
8761 $observe: function(key, fn) {
8762 var attrs = this,
8763 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
8764 listeners = ($$observers[key] || ($$observers[key] = []));
8765
8766 listeners.push(fn);
8767 $rootScope.$evalAsync(function() {
8768 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
8769 // no one registered attribute interpolation function, so lets call it manually
8770 fn(attrs[key]);
8771 }
8772 });
8773
8774 return function() {
8775 arrayRemove(listeners, fn);
8776 };
8777 }
8778 };
8779
8780 function setSpecialAttr(element, attrName, value) {
8781 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
8782 // so we have to jump through some hoops to get such an attribute
8783 // https://github.com/angular/angular.js/pull/13318
Ed Tanous4758d5b2017-06-06 15:28:13 -07008784 specialAttrHolder.innerHTML = '<span ' + attrName + '>';
Ed Tanous904063f2017-03-02 16:48:24 -08008785 var attributes = specialAttrHolder.firstChild.attributes;
8786 var attribute = attributes[0];
8787 // We have to remove the attribute from its container element before we can add it to the destination element
8788 attributes.removeNamedItem(attribute.name);
8789 attribute.value = value;
8790 element.attributes.setNamedItem(attribute);
8791 }
8792
8793 function safeAddClass($element, className) {
8794 try {
8795 $element.addClass(className);
8796 } catch (e) {
8797 // ignore, since it means that we are trying to set class on
8798 // SVG element, where class name is read-only.
8799 }
8800 }
8801
8802
8803 var startSymbol = $interpolate.startSymbol(),
8804 endSymbol = $interpolate.endSymbol(),
Ed Tanous4758d5b2017-06-06 15:28:13 -07008805 denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}')
Ed Tanous904063f2017-03-02 16:48:24 -08008806 ? identity
8807 : function denormalizeTemplate(template) {
8808 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
8809 },
8810 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
8811 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
8812
8813 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
8814 var bindings = $element.data('$binding') || [];
8815
8816 if (isArray(binding)) {
8817 bindings = bindings.concat(binding);
8818 } else {
8819 bindings.push(binding);
8820 }
8821
8822 $element.data('$binding', bindings);
8823 } : noop;
8824
8825 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
8826 safeAddClass($element, 'ng-binding');
8827 } : noop;
8828
8829 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
8830 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
8831 $element.data(dataName, scope);
8832 } : noop;
8833
8834 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
8835 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
8836 } : noop;
8837
8838 compile.$$createComment = function(directiveName, comment) {
8839 var content = '';
8840 if (debugInfoEnabled) {
8841 content = ' ' + (directiveName || '') + ': ';
8842 if (comment) content += comment + ' ';
8843 }
8844 return window.document.createComment(content);
8845 };
8846
8847 return compile;
8848
8849 //================================
8850
8851 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
8852 previousCompileContext) {
8853 if (!($compileNodes instanceof jqLite)) {
8854 // jquery always rewraps, whereas we need to preserve the original selector so that we can
8855 // modify it.
8856 $compileNodes = jqLite($compileNodes);
8857 }
Ed Tanous904063f2017-03-02 16:48:24 -08008858 var compositeLinkFn =
8859 compileNodes($compileNodes, transcludeFn, $compileNodes,
8860 maxPriority, ignoreDirective, previousCompileContext);
8861 compile.$$addScopeClass($compileNodes);
8862 var namespace = null;
8863 return function publicLinkFn(scope, cloneConnectFn, options) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07008864 if (!$compileNodes) {
8865 throw $compileMinErr('multilink', 'This element has already been linked.');
8866 }
Ed Tanous904063f2017-03-02 16:48:24 -08008867 assertArg(scope, 'scope');
8868
8869 if (previousCompileContext && previousCompileContext.needsNewScope) {
8870 // A parent directive did a replace and a directive on this element asked
8871 // for transclusion, which caused us to lose a layer of element on which
8872 // we could hold the new transclusion scope, so we will create it manually
8873 // here.
8874 scope = scope.$parent.$new();
8875 }
8876
8877 options = options || {};
8878 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
8879 transcludeControllers = options.transcludeControllers,
8880 futureParentElement = options.futureParentElement;
8881
8882 // When `parentBoundTranscludeFn` is passed, it is a
8883 // `controllersBoundTransclude` function (it was previously passed
8884 // as `transclude` to directive.link) so we must unwrap it to get
8885 // its `boundTranscludeFn`
8886 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
8887 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
8888 }
8889
8890 if (!namespace) {
8891 namespace = detectNamespaceForChildElements(futureParentElement);
8892 }
8893 var $linkNode;
8894 if (namespace !== 'html') {
8895 // When using a directive with replace:true and templateUrl the $compileNodes
8896 // (or a child element inside of them)
8897 // might change, so we need to recreate the namespace adapted compileNodes
8898 // for call to the link function.
8899 // Note: This will already clone the nodes...
8900 $linkNode = jqLite(
8901 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
8902 );
8903 } else if (cloneConnectFn) {
8904 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
8905 // and sometimes changes the structure of the DOM.
8906 $linkNode = JQLitePrototype.clone.call($compileNodes);
8907 } else {
8908 $linkNode = $compileNodes;
8909 }
8910
8911 if (transcludeControllers) {
8912 for (var controllerName in transcludeControllers) {
8913 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
8914 }
8915 }
8916
8917 compile.$$addScopeInfo($linkNode, scope);
8918
8919 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
8920 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
Ed Tanous4758d5b2017-06-06 15:28:13 -07008921
8922 if (!cloneConnectFn) {
8923 $compileNodes = compositeLinkFn = null;
8924 }
Ed Tanous904063f2017-03-02 16:48:24 -08008925 return $linkNode;
8926 };
8927 }
8928
8929 function detectNamespaceForChildElements(parentElement) {
8930 // TODO: Make this detect MathML as well...
8931 var node = parentElement && parentElement[0];
8932 if (!node) {
8933 return 'html';
8934 } else {
8935 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
8936 }
8937 }
8938
8939 /**
8940 * Compile function matches each node in nodeList against the directives. Once all directives
8941 * for a particular node are collected their compile functions are executed. The compile
8942 * functions return values - the linking functions - are combined into a composite linking
8943 * function, which is the a linking function for the node.
8944 *
8945 * @param {NodeList} nodeList an array of nodes or NodeList to compile
8946 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
8947 * scope argument is auto-generated to the new child of the transcluded parent scope.
8948 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
8949 * the rootElement must be set the jqLite collection of the compile root. This is
8950 * needed so that the jqLite collection items can be replaced with widgets.
8951 * @param {number=} maxPriority Max directive priority.
8952 * @returns {Function} A composite linking function of all of the matched directives or null.
8953 */
8954 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
8955 previousCompileContext) {
8956 var linkFns = [],
Ed Tanous4758d5b2017-06-06 15:28:13 -07008957 // `nodeList` can be either an element's `.childNodes` (live NodeList)
8958 // or a jqLite/jQuery collection or an array
8959 notLiveList = isArray(nodeList) || (nodeList instanceof jqLite),
Ed Tanous904063f2017-03-02 16:48:24 -08008960 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
8961
Ed Tanous4758d5b2017-06-06 15:28:13 -07008962
Ed Tanous904063f2017-03-02 16:48:24 -08008963 for (var i = 0; i < nodeList.length; i++) {
8964 attrs = new Attributes();
8965
Ed Tanous4758d5b2017-06-06 15:28:13 -07008966 // Support: IE 11 only
8967 // Workaround for #11781 and #14924
8968 if (msie === 11) {
8969 mergeConsecutiveTextNodes(nodeList, i, notLiveList);
8970 }
8971
8972 // We must always refer to `nodeList[i]` hereafter,
8973 // since the nodes can be replaced underneath us.
Ed Tanous904063f2017-03-02 16:48:24 -08008974 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
8975 ignoreDirective);
8976
8977 nodeLinkFn = (directives.length)
8978 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
8979 null, [], [], previousCompileContext)
8980 : null;
8981
8982 if (nodeLinkFn && nodeLinkFn.scope) {
8983 compile.$$addScopeClass(attrs.$$element);
8984 }
8985
8986 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
8987 !(childNodes = nodeList[i].childNodes) ||
8988 !childNodes.length)
8989 ? null
8990 : compileNodes(childNodes,
8991 nodeLinkFn ? (
8992 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
8993 && nodeLinkFn.transclude) : transcludeFn);
8994
8995 if (nodeLinkFn || childLinkFn) {
8996 linkFns.push(i, nodeLinkFn, childLinkFn);
8997 linkFnFound = true;
8998 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
8999 }
9000
9001 //use the previous context only for the first element in the virtual group
9002 previousCompileContext = null;
9003 }
9004
9005 // return a linking function if we have found anything, null otherwise
9006 return linkFnFound ? compositeLinkFn : null;
9007
9008 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
9009 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
9010 var stableNodeList;
9011
9012
9013 if (nodeLinkFnFound) {
9014 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
9015 // offsets don't get screwed up
9016 var nodeListLength = nodeList.length;
9017 stableNodeList = new Array(nodeListLength);
9018
9019 // create a sparse array by only copying the elements which have a linkFn
Ed Tanous4758d5b2017-06-06 15:28:13 -07009020 for (i = 0; i < linkFns.length; i += 3) {
Ed Tanous904063f2017-03-02 16:48:24 -08009021 idx = linkFns[i];
9022 stableNodeList[idx] = nodeList[idx];
9023 }
9024 } else {
9025 stableNodeList = nodeList;
9026 }
9027
9028 for (i = 0, ii = linkFns.length; i < ii;) {
9029 node = stableNodeList[linkFns[i++]];
9030 nodeLinkFn = linkFns[i++];
9031 childLinkFn = linkFns[i++];
9032
9033 if (nodeLinkFn) {
9034 if (nodeLinkFn.scope) {
9035 childScope = scope.$new();
9036 compile.$$addScopeInfo(jqLite(node), childScope);
9037 } else {
9038 childScope = scope;
9039 }
9040
9041 if (nodeLinkFn.transcludeOnThisElement) {
9042 childBoundTranscludeFn = createBoundTranscludeFn(
9043 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
9044
9045 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
9046 childBoundTranscludeFn = parentBoundTranscludeFn;
9047
9048 } else if (!parentBoundTranscludeFn && transcludeFn) {
9049 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
9050
9051 } else {
9052 childBoundTranscludeFn = null;
9053 }
9054
9055 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
9056
9057 } else if (childLinkFn) {
9058 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
9059 }
9060 }
9061 }
9062 }
9063
Ed Tanous4758d5b2017-06-06 15:28:13 -07009064 function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) {
9065 var node = nodeList[idx];
9066 var parent = node.parentNode;
9067 var sibling;
9068
9069 if (node.nodeType !== NODE_TYPE_TEXT) {
9070 return;
9071 }
9072
9073 while (true) {
9074 sibling = parent ? node.nextSibling : nodeList[idx + 1];
9075 if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) {
9076 break;
9077 }
9078
9079 node.nodeValue = node.nodeValue + sibling.nodeValue;
9080
9081 if (sibling.parentNode) {
9082 sibling.parentNode.removeChild(sibling);
9083 }
9084 if (notLiveList && sibling === nodeList[idx + 1]) {
9085 nodeList.splice(idx + 1, 1);
9086 }
9087 }
9088 }
9089
Ed Tanous904063f2017-03-02 16:48:24 -08009090 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
9091 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
9092
9093 if (!transcludedScope) {
9094 transcludedScope = scope.$new(false, containingScope);
9095 transcludedScope.$$transcluded = true;
9096 }
9097
9098 return transcludeFn(transcludedScope, cloneFn, {
9099 parentBoundTranscludeFn: previousBoundTranscludeFn,
9100 transcludeControllers: controllers,
9101 futureParentElement: futureParentElement
9102 });
9103 }
9104
9105 // We need to attach the transclusion slots onto the `boundTranscludeFn`
9106 // so that they are available inside the `controllersBoundTransclude` function
9107 var boundSlots = boundTranscludeFn.$$slots = createMap();
9108 for (var slotName in transcludeFn.$$slots) {
9109 if (transcludeFn.$$slots[slotName]) {
9110 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
9111 } else {
9112 boundSlots[slotName] = null;
9113 }
9114 }
9115
9116 return boundTranscludeFn;
9117 }
9118
9119 /**
9120 * Looks for directives on the given node and adds them to the directive collection which is
9121 * sorted.
9122 *
9123 * @param node Node to search.
9124 * @param directives An array to which the directives are added to. This array is sorted before
9125 * the function returns.
9126 * @param attrs The shared attrs object which is used to populate the normalized attributes.
9127 * @param {number=} maxPriority Max directive priority.
9128 */
9129 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
9130 var nodeType = node.nodeType,
9131 attrsMap = attrs.$attr,
9132 match,
Ed Tanous4758d5b2017-06-06 15:28:13 -07009133 nodeName,
Ed Tanous904063f2017-03-02 16:48:24 -08009134 className;
9135
9136 switch (nodeType) {
9137 case NODE_TYPE_ELEMENT: /* Element */
Ed Tanous4758d5b2017-06-06 15:28:13 -07009138
9139 nodeName = nodeName_(node);
9140
Ed Tanous904063f2017-03-02 16:48:24 -08009141 // use the node name: <directive>
9142 addDirective(directives,
Ed Tanous4758d5b2017-06-06 15:28:13 -07009143 directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
Ed Tanous904063f2017-03-02 16:48:24 -08009144
9145 // iterate over the attributes
9146 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
9147 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
9148 var attrStartName = false;
9149 var attrEndName = false;
9150
9151 attr = nAttrs[j];
9152 name = attr.name;
Ed Tanous4758d5b2017-06-06 15:28:13 -07009153 value = attr.value;
Ed Tanous904063f2017-03-02 16:48:24 -08009154
9155 // support ngAttr attribute binding
9156 ngAttrName = directiveNormalize(name);
Ed Tanous4758d5b2017-06-06 15:28:13 -07009157 isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
9158 if (isNgAttr) {
Ed Tanous904063f2017-03-02 16:48:24 -08009159 name = name.replace(PREFIX_REGEXP, '')
9160 .substr(8).replace(/_(.)/g, function(match, letter) {
9161 return letter.toUpperCase();
9162 });
9163 }
9164
9165 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
9166 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
9167 attrStartName = name;
9168 attrEndName = name.substr(0, name.length - 5) + 'end';
9169 name = name.substr(0, name.length - 6);
9170 }
9171
9172 nName = directiveNormalize(name.toLowerCase());
9173 attrsMap[nName] = name;
9174 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
9175 attrs[nName] = value;
9176 if (getBooleanAttrName(node, nName)) {
9177 attrs[nName] = true; // presence means true
9178 }
9179 }
9180 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
9181 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
9182 attrEndName);
9183 }
9184
Ed Tanous4758d5b2017-06-06 15:28:13 -07009185 if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
9186 // Hidden input elements can have strange behaviour when navigating back to the page
9187 // This tells the browser not to try to cache and reinstate previous values
9188 node.setAttribute('autocomplete', 'off');
9189 }
9190
Ed Tanous904063f2017-03-02 16:48:24 -08009191 // use class as directive
Ed Tanous4758d5b2017-06-06 15:28:13 -07009192 if (!cssClassDirectivesEnabled) break;
Ed Tanous904063f2017-03-02 16:48:24 -08009193 className = node.className;
9194 if (isObject(className)) {
9195 // Maybe SVGAnimatedString
9196 className = className.animVal;
9197 }
9198 if (isString(className) && className !== '') {
Ed Tanous4758d5b2017-06-06 15:28:13 -07009199 while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) {
Ed Tanous904063f2017-03-02 16:48:24 -08009200 nName = directiveNormalize(match[2]);
9201 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
9202 attrs[nName] = trim(match[3]);
9203 }
9204 className = className.substr(match.index + match[0].length);
9205 }
9206 }
9207 break;
9208 case NODE_TYPE_TEXT: /* Text Node */
Ed Tanous904063f2017-03-02 16:48:24 -08009209 addTextInterpolateDirective(directives, node.nodeValue);
9210 break;
9211 case NODE_TYPE_COMMENT: /* Comment */
Ed Tanous4758d5b2017-06-06 15:28:13 -07009212 if (!commentDirectivesEnabled) break;
Ed Tanous904063f2017-03-02 16:48:24 -08009213 collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective);
9214 break;
9215 }
9216
9217 directives.sort(byPriority);
9218 return directives;
9219 }
9220
9221 function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
9222 // function created because of performance, try/catch disables
9223 // the optimization of the whole function #14848
9224 try {
9225 var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
9226 if (match) {
9227 var nName = directiveNormalize(match[1]);
9228 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
9229 attrs[nName] = trim(match[2]);
9230 }
9231 }
9232 } catch (e) {
9233 // turns out that under some circumstances IE9 throws errors when one attempts to read
9234 // comment's node value.
9235 // Just ignore it and continue. (Can't seem to reproduce in test case.)
9236 }
9237 }
9238
9239 /**
Ed Tanous4758d5b2017-06-06 15:28:13 -07009240 * Given a node with a directive-start it collects all of the siblings until it finds
Ed Tanous904063f2017-03-02 16:48:24 -08009241 * directive-end.
9242 * @param node
9243 * @param attrStart
9244 * @param attrEnd
9245 * @returns {*}
9246 */
9247 function groupScan(node, attrStart, attrEnd) {
9248 var nodes = [];
9249 var depth = 0;
9250 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
9251 do {
9252 if (!node) {
9253 throw $compileMinErr('uterdir',
Ed Tanous4758d5b2017-06-06 15:28:13 -07009254 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.',
Ed Tanous904063f2017-03-02 16:48:24 -08009255 attrStart, attrEnd);
9256 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07009257 if (node.nodeType === NODE_TYPE_ELEMENT) {
Ed Tanous904063f2017-03-02 16:48:24 -08009258 if (node.hasAttribute(attrStart)) depth++;
9259 if (node.hasAttribute(attrEnd)) depth--;
9260 }
9261 nodes.push(node);
9262 node = node.nextSibling;
9263 } while (depth > 0);
9264 } else {
9265 nodes.push(node);
9266 }
9267
9268 return jqLite(nodes);
9269 }
9270
9271 /**
9272 * Wrapper for linking function which converts normal linking function into a grouped
9273 * linking function.
9274 * @param linkFn
9275 * @param attrStart
9276 * @param attrEnd
9277 * @returns {Function}
9278 */
9279 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
9280 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) {
9281 element = groupScan(element[0], attrStart, attrEnd);
9282 return linkFn(scope, element, attrs, controllers, transcludeFn);
9283 };
9284 }
9285
9286 /**
9287 * A function generator that is used to support both eager and lazy compilation
9288 * linking function.
9289 * @param eager
9290 * @param $compileNodes
9291 * @param transcludeFn
9292 * @param maxPriority
9293 * @param ignoreDirective
9294 * @param previousCompileContext
9295 * @returns {Function}
9296 */
9297 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
9298 var compiled;
9299
9300 if (eager) {
9301 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
9302 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07009303 return /** @this */ function lazyCompilation() {
Ed Tanous904063f2017-03-02 16:48:24 -08009304 if (!compiled) {
9305 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
9306
9307 // Null out all of these references in order to make them eligible for garbage collection
9308 // since this is a potentially long lived closure
9309 $compileNodes = transcludeFn = previousCompileContext = null;
9310 }
9311 return compiled.apply(this, arguments);
9312 };
9313 }
9314
9315 /**
9316 * Once the directives have been collected, their compile functions are executed. This method
9317 * is responsible for inlining directive templates as well as terminating the application
9318 * of the directives if the terminal directive has been reached.
9319 *
9320 * @param {Array} directives Array of collected directives to execute their compile function.
9321 * this needs to be pre-sorted by priority order.
9322 * @param {Node} compileNode The raw DOM node to apply the compile functions to
9323 * @param {Object} templateAttrs The shared attribute function
9324 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
9325 * scope argument is auto-generated to the new
9326 * child of the transcluded parent scope.
9327 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
9328 * argument has the root jqLite array so that we can replace nodes
9329 * on it.
9330 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
9331 * compiling the transclusion.
9332 * @param {Array.<Function>} preLinkFns
9333 * @param {Array.<Function>} postLinkFns
9334 * @param {Object} previousCompileContext Context used for previous compilation of the current
9335 * node
9336 * @returns {Function} linkFn
9337 */
9338 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
9339 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
9340 previousCompileContext) {
9341 previousCompileContext = previousCompileContext || {};
9342
9343 var terminalPriority = -Number.MAX_VALUE,
9344 newScopeDirective = previousCompileContext.newScopeDirective,
9345 controllerDirectives = previousCompileContext.controllerDirectives,
9346 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
9347 templateDirective = previousCompileContext.templateDirective,
9348 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
9349 hasTranscludeDirective = false,
9350 hasTemplate = false,
9351 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
9352 $compileNode = templateAttrs.$$element = jqLite(compileNode),
9353 directive,
9354 directiveName,
9355 $template,
9356 replaceDirective = originalReplaceDirective,
9357 childTranscludeFn = transcludeFn,
9358 linkFn,
9359 didScanForMultipleTransclusion = false,
9360 mightHaveMultipleTransclusionError = false,
9361 directiveValue;
9362
9363 // executes all directives on the current element
9364 for (var i = 0, ii = directives.length; i < ii; i++) {
9365 directive = directives[i];
9366 var attrStart = directive.$$start;
9367 var attrEnd = directive.$$end;
9368
9369 // collect multiblock sections
9370 if (attrStart) {
9371 $compileNode = groupScan(compileNode, attrStart, attrEnd);
9372 }
9373 $template = undefined;
9374
9375 if (terminalPriority > directive.priority) {
9376 break; // prevent further processing of directives
9377 }
9378
Ed Tanous4758d5b2017-06-06 15:28:13 -07009379 directiveValue = directive.scope;
9380
9381 if (directiveValue) {
Ed Tanous904063f2017-03-02 16:48:24 -08009382
9383 // skip the check for directives with async templates, we'll check the derived sync
9384 // directive when the template arrives
9385 if (!directive.templateUrl) {
9386 if (isObject(directiveValue)) {
9387 // This directive is trying to add an isolated scope.
9388 // Check that there is no scope of any kind already
9389 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
9390 directive, $compileNode);
9391 newIsolateScopeDirective = directive;
9392 } else {
9393 // This directive is trying to add a child scope.
9394 // Check that there is no isolated scope already
9395 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
9396 $compileNode);
9397 }
9398 }
9399
9400 newScopeDirective = newScopeDirective || directive;
9401 }
9402
9403 directiveName = directive.name;
9404
9405 // If we encounter a condition that can result in transclusion on the directive,
9406 // then scan ahead in the remaining directives for others that may cause a multiple
9407 // transclusion error to be thrown during the compilation process. If a matching directive
9408 // is found, then we know that when we encounter a transcluded directive, we need to eagerly
9409 // compile the `transclude` function rather than doing it lazily in order to throw
9410 // exceptions at the correct time
9411 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
9412 || (directive.transclude && !directive.$$tlb))) {
9413 var candidateDirective;
9414
Ed Tanous4758d5b2017-06-06 15:28:13 -07009415 for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) {
Ed Tanous904063f2017-03-02 16:48:24 -08009416 if ((candidateDirective.transclude && !candidateDirective.$$tlb)
9417 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
9418 mightHaveMultipleTransclusionError = true;
9419 break;
9420 }
9421 }
9422
9423 didScanForMultipleTransclusion = true;
9424 }
9425
9426 if (!directive.templateUrl && directive.controller) {
Ed Tanous904063f2017-03-02 16:48:24 -08009427 controllerDirectives = controllerDirectives || createMap();
Ed Tanous4758d5b2017-06-06 15:28:13 -07009428 assertNoDuplicate('\'' + directiveName + '\' controller',
Ed Tanous904063f2017-03-02 16:48:24 -08009429 controllerDirectives[directiveName], directive, $compileNode);
9430 controllerDirectives[directiveName] = directive;
9431 }
9432
Ed Tanous4758d5b2017-06-06 15:28:13 -07009433 directiveValue = directive.transclude;
9434
9435 if (directiveValue) {
Ed Tanous904063f2017-03-02 16:48:24 -08009436 hasTranscludeDirective = true;
9437
9438 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
9439 // This option should only be used by directives that know how to safely handle element transclusion,
9440 // where the transcluded nodes are added or replaced after linking.
9441 if (!directive.$$tlb) {
9442 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
9443 nonTlbTranscludeDirective = directive;
9444 }
9445
Ed Tanous4758d5b2017-06-06 15:28:13 -07009446 if (directiveValue === 'element') {
Ed Tanous904063f2017-03-02 16:48:24 -08009447 hasElementTranscludeDirective = true;
9448 terminalPriority = directive.priority;
9449 $template = $compileNode;
9450 $compileNode = templateAttrs.$$element =
9451 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName]));
9452 compileNode = $compileNode[0];
9453 replaceWith(jqCollection, sliceArgs($template), compileNode);
9454
9455 // Support: Chrome < 50
9456 // https://github.com/angular/angular.js/issues/14041
9457
9458 // In the versions of V8 prior to Chrome 50, the document fragment that is created
9459 // in the `replaceWith` function is improperly garbage collected despite still
9460 // being referenced by the `parentNode` property of all of the child nodes. By adding
9461 // a reference to the fragment via a different property, we can avoid that incorrect
9462 // behavior.
9463 // TODO: remove this line after Chrome 50 has been released
9464 $template[0].$$parentNode = $template[0].parentNode;
9465
9466 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
9467 replaceDirective && replaceDirective.name, {
9468 // Don't pass in:
9469 // - controllerDirectives - otherwise we'll create duplicates controllers
9470 // - newIsolateScopeDirective or templateDirective - combining templates with
9471 // element transclusion doesn't make sense.
9472 //
9473 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
9474 // on the same element more than once.
9475 nonTlbTranscludeDirective: nonTlbTranscludeDirective
9476 });
9477 } else {
9478
9479 var slots = createMap();
9480
Ed Tanous4758d5b2017-06-06 15:28:13 -07009481 if (!isObject(directiveValue)) {
9482 $template = jqLite(jqLiteClone(compileNode)).contents();
9483 } else {
Ed Tanous904063f2017-03-02 16:48:24 -08009484
9485 // We have transclusion slots,
9486 // collect them up, compile them and store their transclusion functions
9487 $template = [];
9488
9489 var slotMap = createMap();
9490 var filledSlots = createMap();
9491
9492 // Parse the element selectors
9493 forEach(directiveValue, function(elementSelector, slotName) {
9494 // If an element selector starts with a ? then it is optional
9495 var optional = (elementSelector.charAt(0) === '?');
9496 elementSelector = optional ? elementSelector.substring(1) : elementSelector;
9497
9498 slotMap[elementSelector] = slotName;
9499
9500 // We explicitly assign `null` since this implies that a slot was defined but not filled.
9501 // Later when calling boundTransclusion functions with a slot name we only error if the
9502 // slot is `undefined`
9503 slots[slotName] = null;
9504
9505 // filledSlots contains `true` for all slots that are either optional or have been
9506 // filled. This is used to check that we have not missed any required slots
9507 filledSlots[slotName] = optional;
9508 });
9509
9510 // Add the matching elements into their slot
9511 forEach($compileNode.contents(), function(node) {
9512 var slotName = slotMap[directiveNormalize(nodeName_(node))];
9513 if (slotName) {
9514 filledSlots[slotName] = true;
9515 slots[slotName] = slots[slotName] || [];
9516 slots[slotName].push(node);
9517 } else {
9518 $template.push(node);
9519 }
9520 });
9521
9522 // Check for required slots that were not filled
9523 forEach(filledSlots, function(filled, slotName) {
9524 if (!filled) {
9525 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
9526 }
9527 });
9528
9529 for (var slotName in slots) {
9530 if (slots[slotName]) {
9531 // Only define a transclusion function if the slot was filled
9532 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
9533 }
9534 }
9535 }
9536
9537 $compileNode.empty(); // clear contents
9538 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
9539 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
9540 childTranscludeFn.$$slots = slots;
9541 }
9542 }
9543
9544 if (directive.template) {
9545 hasTemplate = true;
9546 assertNoDuplicate('template', templateDirective, directive, $compileNode);
9547 templateDirective = directive;
9548
9549 directiveValue = (isFunction(directive.template))
9550 ? directive.template($compileNode, templateAttrs)
9551 : directive.template;
9552
9553 directiveValue = denormalizeTemplate(directiveValue);
9554
9555 if (directive.replace) {
9556 replaceDirective = directive;
9557 if (jqLiteIsTextNode(directiveValue)) {
9558 $template = [];
9559 } else {
9560 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
9561 }
9562 compileNode = $template[0];
9563
Ed Tanous4758d5b2017-06-06 15:28:13 -07009564 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
Ed Tanous904063f2017-03-02 16:48:24 -08009565 throw $compileMinErr('tplrt',
Ed Tanous4758d5b2017-06-06 15:28:13 -07009566 'Template for directive \'{0}\' must have exactly one root element. {1}',
Ed Tanous904063f2017-03-02 16:48:24 -08009567 directiveName, '');
9568 }
9569
9570 replaceWith(jqCollection, $compileNode, compileNode);
9571
9572 var newTemplateAttrs = {$attr: {}};
9573
9574 // combine directives from the original node and from the template:
9575 // - take the array of directives for this element
9576 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
9577 // - collect directives from the template and sort them by priority
9578 // - combine directives as: processed + template + unprocessed
9579 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
9580 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
9581
9582 if (newIsolateScopeDirective || newScopeDirective) {
9583 // The original directive caused the current element to be replaced but this element
9584 // also needs to have a new scope, so we need to tell the template directives
9585 // that they would need to get their scope from further up, if they require transclusion
9586 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
9587 }
9588 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
9589 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
9590
9591 ii = directives.length;
9592 } else {
9593 $compileNode.html(directiveValue);
9594 }
9595 }
9596
9597 if (directive.templateUrl) {
9598 hasTemplate = true;
9599 assertNoDuplicate('template', templateDirective, directive, $compileNode);
9600 templateDirective = directive;
9601
9602 if (directive.replace) {
9603 replaceDirective = directive;
9604 }
9605
Ed Tanous4758d5b2017-06-06 15:28:13 -07009606 // eslint-disable-next-line no-func-assign
Ed Tanous904063f2017-03-02 16:48:24 -08009607 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
Ed Tanous904063f2017-03-02 16:48:24 -08009608 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
9609 controllerDirectives: controllerDirectives,
9610 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
9611 newIsolateScopeDirective: newIsolateScopeDirective,
9612 templateDirective: templateDirective,
9613 nonTlbTranscludeDirective: nonTlbTranscludeDirective
9614 });
9615 ii = directives.length;
9616 } else if (directive.compile) {
9617 try {
9618 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
9619 var context = directive.$$originalDirective || directive;
9620 if (isFunction(linkFn)) {
9621 addLinkFns(null, bind(context, linkFn), attrStart, attrEnd);
9622 } else if (linkFn) {
9623 addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd);
9624 }
9625 } catch (e) {
9626 $exceptionHandler(e, startingTag($compileNode));
9627 }
9628 }
9629
9630 if (directive.terminal) {
9631 nodeLinkFn.terminal = true;
9632 terminalPriority = Math.max(terminalPriority, directive.priority);
9633 }
9634
9635 }
9636
9637 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
9638 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
9639 nodeLinkFn.templateOnThisElement = hasTemplate;
9640 nodeLinkFn.transclude = childTranscludeFn;
9641
9642 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
9643
9644 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
9645 return nodeLinkFn;
9646
9647 ////////////////////
9648
9649 function addLinkFns(pre, post, attrStart, attrEnd) {
9650 if (pre) {
9651 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
9652 pre.require = directive.require;
9653 pre.directiveName = directiveName;
9654 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
9655 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
9656 }
9657 preLinkFns.push(pre);
9658 }
9659 if (post) {
9660 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
9661 post.require = directive.require;
9662 post.directiveName = directiveName;
9663 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
9664 post = cloneAndAnnotateFn(post, {isolateScope: true});
9665 }
9666 postLinkFns.push(post);
9667 }
9668 }
9669
9670 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
9671 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
9672 attrs, scopeBindingInfo;
9673
9674 if (compileNode === linkNode) {
9675 attrs = templateAttrs;
9676 $element = templateAttrs.$$element;
9677 } else {
9678 $element = jqLite(linkNode);
9679 attrs = new Attributes($element, templateAttrs);
9680 }
9681
9682 controllerScope = scope;
9683 if (newIsolateScopeDirective) {
9684 isolateScope = scope.$new(true);
9685 } else if (newScopeDirective) {
9686 controllerScope = scope.$parent;
9687 }
9688
9689 if (boundTranscludeFn) {
9690 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
9691 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
9692 transcludeFn = controllersBoundTransclude;
9693 transcludeFn.$$boundTransclude = boundTranscludeFn;
9694 // expose the slots on the `$transclude` function
9695 transcludeFn.isSlotFilled = function(slotName) {
9696 return !!boundTranscludeFn.$$slots[slotName];
9697 };
9698 }
9699
9700 if (controllerDirectives) {
9701 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
9702 }
9703
9704 if (newIsolateScopeDirective) {
9705 // Initialize isolate scope bindings for new isolate scope directive.
9706 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
9707 templateDirective === newIsolateScopeDirective.$$originalDirective)));
9708 compile.$$addScopeClass($element, true);
9709 isolateScope.$$isolateBindings =
9710 newIsolateScopeDirective.$$isolateBindings;
9711 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
9712 isolateScope.$$isolateBindings,
9713 newIsolateScopeDirective);
9714 if (scopeBindingInfo.removeWatches) {
9715 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
9716 }
9717 }
9718
9719 // Initialize bindToController bindings
9720 for (var name in elementControllers) {
9721 var controllerDirective = controllerDirectives[name];
9722 var controller = elementControllers[name];
9723 var bindings = controllerDirective.$$bindings.bindToController;
9724
Ed Tanous4758d5b2017-06-06 15:28:13 -07009725 if (preAssignBindingsEnabled) {
9726 if (bindings) {
9727 controller.bindingInfo =
9728 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9729 } else {
9730 controller.bindingInfo = {};
9731 }
Ed Tanous904063f2017-03-02 16:48:24 -08009732
Ed Tanous4758d5b2017-06-06 15:28:13 -07009733 var controllerResult = controller();
9734 if (controllerResult !== controller.instance) {
9735 // If the controller constructor has a return value, overwrite the instance
9736 // from setupControllers
9737 controller.instance = controllerResult;
9738 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
9739 if (controller.bindingInfo.removeWatches) {
9740 controller.bindingInfo.removeWatches();
9741 }
9742 controller.bindingInfo =
9743 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9744 }
9745 } else {
9746 controller.instance = controller();
9747 $element.data('$' + controllerDirective.name + 'Controller', controller.instance);
Ed Tanous904063f2017-03-02 16:48:24 -08009748 controller.bindingInfo =
9749 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9750 }
9751 }
9752
9753 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
9754 forEach(controllerDirectives, function(controllerDirective, name) {
9755 var require = controllerDirective.require;
9756 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
9757 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
9758 }
9759 });
9760
9761 // Handle the init and destroy lifecycle hooks on all controllers that have them
9762 forEach(elementControllers, function(controller) {
9763 var controllerInstance = controller.instance;
9764 if (isFunction(controllerInstance.$onChanges)) {
9765 try {
9766 controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
9767 } catch (e) {
9768 $exceptionHandler(e);
9769 }
9770 }
9771 if (isFunction(controllerInstance.$onInit)) {
9772 try {
9773 controllerInstance.$onInit();
9774 } catch (e) {
9775 $exceptionHandler(e);
9776 }
9777 }
9778 if (isFunction(controllerInstance.$doCheck)) {
9779 controllerScope.$watch(function() { controllerInstance.$doCheck(); });
9780 controllerInstance.$doCheck();
9781 }
9782 if (isFunction(controllerInstance.$onDestroy)) {
9783 controllerScope.$on('$destroy', function callOnDestroyHook() {
9784 controllerInstance.$onDestroy();
9785 });
9786 }
9787 });
9788
9789 // PRELINKING
9790 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
9791 linkFn = preLinkFns[i];
9792 invokeLinkFn(linkFn,
9793 linkFn.isolateScope ? isolateScope : scope,
9794 $element,
9795 attrs,
9796 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
9797 transcludeFn
9798 );
9799 }
9800
9801 // RECURSION
9802 // We only pass the isolate scope, if the isolate directive has a template,
9803 // otherwise the child elements do not belong to the isolate directive.
9804 var scopeToChild = scope;
9805 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
9806 scopeToChild = isolateScope;
9807 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07009808 if (childLinkFn) {
9809 childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
9810 }
Ed Tanous904063f2017-03-02 16:48:24 -08009811
9812 // POSTLINKING
9813 for (i = postLinkFns.length - 1; i >= 0; i--) {
9814 linkFn = postLinkFns[i];
9815 invokeLinkFn(linkFn,
9816 linkFn.isolateScope ? isolateScope : scope,
9817 $element,
9818 attrs,
9819 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
9820 transcludeFn
9821 );
9822 }
9823
9824 // Trigger $postLink lifecycle hooks
9825 forEach(elementControllers, function(controller) {
9826 var controllerInstance = controller.instance;
9827 if (isFunction(controllerInstance.$postLink)) {
9828 controllerInstance.$postLink();
9829 }
9830 });
9831
9832 // This is the function that is injected as `$transclude`.
9833 // Note: all arguments are optional!
9834 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
9835 var transcludeControllers;
9836 // No scope passed in:
9837 if (!isScope(scope)) {
9838 slotName = futureParentElement;
9839 futureParentElement = cloneAttachFn;
9840 cloneAttachFn = scope;
9841 scope = undefined;
9842 }
9843
9844 if (hasElementTranscludeDirective) {
9845 transcludeControllers = elementControllers;
9846 }
9847 if (!futureParentElement) {
9848 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
9849 }
9850 if (slotName) {
9851 // slotTranscludeFn can be one of three things:
9852 // * a transclude function - a filled slot
9853 // * `null` - an optional slot that was not filled
9854 // * `undefined` - a slot that was not declared (i.e. invalid)
9855 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
9856 if (slotTranscludeFn) {
9857 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
9858 } else if (isUndefined(slotTranscludeFn)) {
9859 throw $compileMinErr('noslot',
9860 'No parent directive that requires a transclusion with slot name "{0}". ' +
9861 'Element: {1}',
9862 slotName, startingTag($element));
9863 }
9864 } else {
9865 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
9866 }
9867 }
9868 }
9869 }
9870
9871 function getControllers(directiveName, require, $element, elementControllers) {
9872 var value;
9873
9874 if (isString(require)) {
9875 var match = require.match(REQUIRE_PREFIX_REGEXP);
9876 var name = require.substring(match[0].length);
9877 var inheritType = match[1] || match[3];
9878 var optional = match[2] === '?';
9879
9880 //If only parents then start at the parent element
9881 if (inheritType === '^^') {
9882 $element = $element.parent();
9883 //Otherwise attempt getting the controller from elementControllers in case
9884 //the element is transcluded (and has no data) and to avoid .data if possible
9885 } else {
9886 value = elementControllers && elementControllers[name];
9887 value = value && value.instance;
9888 }
9889
9890 if (!value) {
9891 var dataName = '$' + name + 'Controller';
9892 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
9893 }
9894
9895 if (!value && !optional) {
9896 throw $compileMinErr('ctreq',
Ed Tanous4758d5b2017-06-06 15:28:13 -07009897 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!',
Ed Tanous904063f2017-03-02 16:48:24 -08009898 name, directiveName);
9899 }
9900 } else if (isArray(require)) {
9901 value = [];
9902 for (var i = 0, ii = require.length; i < ii; i++) {
9903 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
9904 }
9905 } else if (isObject(require)) {
9906 value = {};
9907 forEach(require, function(controller, property) {
9908 value[property] = getControllers(directiveName, controller, $element, elementControllers);
9909 });
9910 }
9911
9912 return value || null;
9913 }
9914
9915 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
9916 var elementControllers = createMap();
9917 for (var controllerKey in controllerDirectives) {
9918 var directive = controllerDirectives[controllerKey];
9919 var locals = {
9920 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
9921 $element: $element,
9922 $attrs: attrs,
9923 $transclude: transcludeFn
9924 };
9925
9926 var controller = directive.controller;
Ed Tanous4758d5b2017-06-06 15:28:13 -07009927 if (controller === '@') {
Ed Tanous904063f2017-03-02 16:48:24 -08009928 controller = attrs[directive.name];
9929 }
9930
9931 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
9932
9933 // For directives with element transclusion the element is a comment.
9934 // In this case .data will not attach any data.
9935 // Instead, we save the controllers for the element in a local hash and attach to .data
9936 // later, once we have the actual element.
9937 elementControllers[directive.name] = controllerInstance;
9938 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
9939 }
9940 return elementControllers;
9941 }
9942
9943 // Depending upon the context in which a directive finds itself it might need to have a new isolated
9944 // or child scope created. For instance:
9945 // * if the directive has been pulled into a template because another directive with a higher priority
9946 // asked for element transclusion
9947 // * if the directive itself asks for transclusion but it is at the root of a template and the original
9948 // element was replaced. See https://github.com/angular/angular.js/issues/12936
9949 function markDirectiveScope(directives, isolateScope, newScope) {
9950 for (var j = 0, jj = directives.length; j < jj; j++) {
9951 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
9952 }
9953 }
9954
9955 /**
9956 * looks up the directive and decorates it with exception handling and proper parameters. We
9957 * call this the boundDirective.
9958 *
9959 * @param {string} name name of the directive to look up.
9960 * @param {string} location The directive must be found in specific format.
9961 * String containing any of theses characters:
9962 *
9963 * * `E`: element name
9964 * * `A': attribute
9965 * * `C`: class
9966 * * `M`: comment
9967 * @returns {boolean} true if directive was added.
9968 */
9969 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
9970 endAttrName) {
9971 if (name === ignoreDirective) return null;
9972 var match = null;
9973 if (hasDirectives.hasOwnProperty(name)) {
9974 for (var directive, directives = $injector.get(name + Suffix),
9975 i = 0, ii = directives.length; i < ii; i++) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07009976 directive = directives[i];
9977 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
9978 directive.restrict.indexOf(location) !== -1) {
9979 if (startAttrName) {
9980 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
Ed Tanous904063f2017-03-02 16:48:24 -08009981 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07009982 if (!directive.$$bindings) {
9983 var bindings = directive.$$bindings =
9984 parseDirectiveBindings(directive, directive.name);
9985 if (isObject(bindings.isolateScope)) {
9986 directive.$$isolateBindings = bindings.isolateScope;
9987 }
9988 }
9989 tDirectives.push(directive);
9990 match = directive;
9991 }
Ed Tanous904063f2017-03-02 16:48:24 -08009992 }
9993 }
9994 return match;
9995 }
9996
9997
9998 /**
9999 * looks up the directive and returns true if it is a multi-element directive,
10000 * and therefore requires DOM nodes between -start and -end markers to be grouped
10001 * together.
10002 *
10003 * @param {string} name name of the directive to look up.
10004 * @returns true if directive was registered as multi-element.
10005 */
10006 function directiveIsMultiElement(name) {
10007 if (hasDirectives.hasOwnProperty(name)) {
10008 for (var directive, directives = $injector.get(name + Suffix),
10009 i = 0, ii = directives.length; i < ii; i++) {
10010 directive = directives[i];
10011 if (directive.multiElement) {
10012 return true;
10013 }
10014 }
10015 }
10016 return false;
10017 }
10018
10019 /**
10020 * When the element is replaced with HTML template then the new attributes
10021 * on the template need to be merged with the existing attributes in the DOM.
10022 * The desired effect is to have both of the attributes present.
10023 *
10024 * @param {object} dst destination attributes (original DOM)
10025 * @param {object} src source attributes (from the directive template)
10026 */
10027 function mergeTemplateAttributes(dst, src) {
10028 var srcAttr = src.$attr,
Ed Tanous4758d5b2017-06-06 15:28:13 -070010029 dstAttr = dst.$attr;
Ed Tanous904063f2017-03-02 16:48:24 -080010030
10031 // reapply the old attributes to the new element
10032 forEach(dst, function(value, key) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010033 if (key.charAt(0) !== '$') {
Ed Tanous904063f2017-03-02 16:48:24 -080010034 if (src[key] && src[key] !== value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010035 if (value.length) {
10036 value += (key === 'style' ? ';' : ' ') + src[key];
10037 } else {
10038 value = src[key];
10039 }
Ed Tanous904063f2017-03-02 16:48:24 -080010040 }
10041 dst.$set(key, value, true, srcAttr[key]);
10042 }
10043 });
10044
10045 // copy the new attributes on the old attrs object
10046 forEach(src, function(value, key) {
10047 // Check if we already set this attribute in the loop above.
10048 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
10049 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
10050 // have an attribute like "has-own-property" or "data-has-own-property", etc.
10051 if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
10052 dst[key] = value;
10053
10054 if (key !== 'class' && key !== 'style') {
10055 dstAttr[key] = srcAttr[key];
10056 }
10057 }
10058 });
10059 }
10060
10061
10062 function compileTemplateUrl(directives, $compileNode, tAttrs,
10063 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
10064 var linkQueue = [],
10065 afterTemplateNodeLinkFn,
10066 afterTemplateChildLinkFn,
10067 beforeTemplateCompileNode = $compileNode[0],
10068 origAsyncDirective = directives.shift(),
10069 derivedSyncDirective = inherit(origAsyncDirective, {
10070 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
10071 }),
10072 templateUrl = (isFunction(origAsyncDirective.templateUrl))
10073 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
10074 : origAsyncDirective.templateUrl,
10075 templateNamespace = origAsyncDirective.templateNamespace;
10076
10077 $compileNode.empty();
10078
10079 $templateRequest(templateUrl)
10080 .then(function(content) {
10081 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
10082
10083 content = denormalizeTemplate(content);
10084
10085 if (origAsyncDirective.replace) {
10086 if (jqLiteIsTextNode(content)) {
10087 $template = [];
10088 } else {
10089 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
10090 }
10091 compileNode = $template[0];
10092
Ed Tanous4758d5b2017-06-06 15:28:13 -070010093 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
Ed Tanous904063f2017-03-02 16:48:24 -080010094 throw $compileMinErr('tplrt',
Ed Tanous4758d5b2017-06-06 15:28:13 -070010095 'Template for directive \'{0}\' must have exactly one root element. {1}',
Ed Tanous904063f2017-03-02 16:48:24 -080010096 origAsyncDirective.name, templateUrl);
10097 }
10098
10099 tempTemplateAttrs = {$attr: {}};
10100 replaceWith($rootElement, $compileNode, compileNode);
10101 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
10102
10103 if (isObject(origAsyncDirective.scope)) {
10104 // the original directive that caused the template to be loaded async required
10105 // an isolate scope
10106 markDirectiveScope(templateDirectives, true);
10107 }
10108 directives = templateDirectives.concat(directives);
10109 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
10110 } else {
10111 compileNode = beforeTemplateCompileNode;
10112 $compileNode.html(content);
10113 }
10114
10115 directives.unshift(derivedSyncDirective);
10116
10117 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
10118 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
10119 previousCompileContext);
10120 forEach($rootElement, function(node, i) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010121 if (node === compileNode) {
Ed Tanous904063f2017-03-02 16:48:24 -080010122 $rootElement[i] = $compileNode[0];
10123 }
10124 });
10125 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
10126
10127 while (linkQueue.length) {
10128 var scope = linkQueue.shift(),
10129 beforeTemplateLinkNode = linkQueue.shift(),
10130 linkRootElement = linkQueue.shift(),
10131 boundTranscludeFn = linkQueue.shift(),
10132 linkNode = $compileNode[0];
10133
10134 if (scope.$$destroyed) continue;
10135
10136 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
10137 var oldClasses = beforeTemplateLinkNode.className;
10138
10139 if (!(previousCompileContext.hasElementTranscludeDirective &&
10140 origAsyncDirective.replace)) {
10141 // it was cloned therefore we have to clone as well.
10142 linkNode = jqLiteClone(compileNode);
10143 }
10144 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
10145
10146 // Copy in CSS classes from original node
10147 safeAddClass(jqLite(linkNode), oldClasses);
10148 }
10149 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
10150 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
10151 } else {
10152 childBoundTranscludeFn = boundTranscludeFn;
10153 }
10154 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
10155 childBoundTranscludeFn);
10156 }
10157 linkQueue = null;
Ed Tanous4758d5b2017-06-06 15:28:13 -070010158 }).catch(function(error) {
10159 if (error instanceof Error) {
10160 $exceptionHandler(error);
10161 }
Ed Tanous904063f2017-03-02 16:48:24 -080010162 });
10163
10164 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
10165 var childBoundTranscludeFn = boundTranscludeFn;
10166 if (scope.$$destroyed) return;
10167 if (linkQueue) {
10168 linkQueue.push(scope,
10169 node,
10170 rootElement,
10171 childBoundTranscludeFn);
10172 } else {
10173 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
10174 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
10175 }
10176 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
10177 }
10178 };
10179 }
10180
10181
10182 /**
10183 * Sorting function for bound directives.
10184 */
10185 function byPriority(a, b) {
10186 var diff = b.priority - a.priority;
10187 if (diff !== 0) return diff;
10188 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
10189 return a.index - b.index;
10190 }
10191
10192 function assertNoDuplicate(what, previousDirective, directive, element) {
10193
10194 function wrapModuleNameIfDefined(moduleName) {
10195 return moduleName ?
10196 (' (module: ' + moduleName + ')') :
10197 '';
10198 }
10199
10200 if (previousDirective) {
10201 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
10202 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
10203 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
10204 }
10205 }
10206
10207
10208 function addTextInterpolateDirective(directives, text) {
10209 var interpolateFn = $interpolate(text, true);
10210 if (interpolateFn) {
10211 directives.push({
10212 priority: 0,
10213 compile: function textInterpolateCompileFn(templateNode) {
10214 var templateNodeParent = templateNode.parent(),
10215 hasCompileParent = !!templateNodeParent.length;
10216
10217 // When transcluding a template that has bindings in the root
10218 // we don't have a parent and thus need to add the class during linking fn.
10219 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
10220
10221 return function textInterpolateLinkFn(scope, node) {
10222 var parent = node.parent();
10223 if (!hasCompileParent) compile.$$addBindingClass(parent);
10224 compile.$$addBindingInfo(parent, interpolateFn.expressions);
10225 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
10226 node[0].nodeValue = value;
10227 });
10228 };
10229 }
10230 });
10231 }
10232 }
10233
10234
10235 function wrapTemplate(type, template) {
10236 type = lowercase(type || 'html');
10237 switch (type) {
10238 case 'svg':
10239 case 'math':
10240 var wrapper = window.document.createElement('div');
10241 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
10242 return wrapper.childNodes[0].childNodes;
10243 default:
10244 return template;
10245 }
10246 }
10247
10248
10249 function getTrustedContext(node, attrNormalizedName) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010250 if (attrNormalizedName === 'srcdoc') {
Ed Tanous904063f2017-03-02 16:48:24 -080010251 return $sce.HTML;
10252 }
10253 var tag = nodeName_(node);
Ed Tanous4758d5b2017-06-06 15:28:13 -070010254 // All tags with src attributes require a RESOURCE_URL value, except for
10255 // img and various html5 media tags.
10256 if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
10257 if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
10258 return $sce.RESOURCE_URL;
10259 }
Ed Tanous904063f2017-03-02 16:48:24 -080010260 // maction[xlink:href] can source SVG. It's not limited to <maction>.
Ed Tanous4758d5b2017-06-06 15:28:13 -070010261 } else if (attrNormalizedName === 'xlinkHref' ||
10262 (tag === 'form' && attrNormalizedName === 'action') ||
10263 // links can be stylesheets or imports, which can run script in the current origin
10264 (tag === 'link' && attrNormalizedName === 'href')
10265 ) {
Ed Tanous904063f2017-03-02 16:48:24 -080010266 return $sce.RESOURCE_URL;
10267 }
10268 }
10269
10270
Ed Tanous4758d5b2017-06-06 15:28:13 -070010271 function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
Ed Tanous904063f2017-03-02 16:48:24 -080010272 var trustedContext = getTrustedContext(node, name);
Ed Tanous4758d5b2017-06-06 15:28:13 -070010273 var mustHaveExpression = !isNgAttr;
10274 var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
Ed Tanous904063f2017-03-02 16:48:24 -080010275
Ed Tanous4758d5b2017-06-06 15:28:13 -070010276 var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
Ed Tanous904063f2017-03-02 16:48:24 -080010277
10278 // no interpolation found -> ignore
10279 if (!interpolateFn) return;
10280
Ed Tanous4758d5b2017-06-06 15:28:13 -070010281 if (name === 'multiple' && nodeName_(node) === 'select') {
10282 throw $compileMinErr('selmulti',
10283 'Binding to the \'multiple\' attribute is not supported. Element: {0}',
Ed Tanous904063f2017-03-02 16:48:24 -080010284 startingTag(node));
10285 }
10286
Ed Tanous4758d5b2017-06-06 15:28:13 -070010287 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
10288 throw $compileMinErr('nodomevents',
10289 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' +
10290 'ng- versions (such as ng-click instead of onclick) instead.');
10291 }
10292
Ed Tanous904063f2017-03-02 16:48:24 -080010293 directives.push({
10294 priority: 100,
10295 compile: function() {
10296 return {
10297 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
10298 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
10299
Ed Tanous904063f2017-03-02 16:48:24 -080010300 // If the attribute has changed since last $interpolate()ed
10301 var newValue = attr[name];
10302 if (newValue !== value) {
10303 // we need to interpolate again since the attribute value has been updated
10304 // (e.g. by another directive's compile function)
10305 // ensure unset/empty values make interpolateFn falsy
10306 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
10307 value = newValue;
10308 }
10309
10310 // if attribute was updated so that there is no interpolation going on we don't want to
10311 // register any observers
10312 if (!interpolateFn) return;
10313
10314 // initialize attr object so that it's ready in case we need the value for isolate
10315 // scope initialization, otherwise the value would not be available from isolate
10316 // directive's linking fn during linking phase
10317 attr[name] = interpolateFn(scope);
10318
10319 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
10320 (attr.$$observers && attr.$$observers[name].$$scope || scope).
10321 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
10322 //special case for class attribute addition + removal
10323 //so that class changes can tap into the animation
10324 //hooks provided by the $animate service. Be sure to
10325 //skip animations when the first digest occurs (when
10326 //both the new and the old values are the same) since
10327 //the CSS classes are the non-interpolated values
Ed Tanous4758d5b2017-06-06 15:28:13 -070010328 if (name === 'class' && newValue !== oldValue) {
Ed Tanous904063f2017-03-02 16:48:24 -080010329 attr.$updateClass(newValue, oldValue);
10330 } else {
10331 attr.$set(name, newValue);
10332 }
10333 });
10334 }
10335 };
10336 }
10337 });
10338 }
10339
10340
10341 /**
10342 * This is a special jqLite.replaceWith, which can replace items which
10343 * have no parents, provided that the containing jqLite collection is provided.
10344 *
10345 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
10346 * in the root of the tree.
10347 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
10348 * the shell, but replace its DOM node reference.
10349 * @param {Node} newNode The new DOM node.
10350 */
10351 function replaceWith($rootElement, elementsToRemove, newNode) {
10352 var firstElementToRemove = elementsToRemove[0],
10353 removeCount = elementsToRemove.length,
10354 parent = firstElementToRemove.parentNode,
10355 i, ii;
10356
10357 if ($rootElement) {
10358 for (i = 0, ii = $rootElement.length; i < ii; i++) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010359 if ($rootElement[i] === firstElementToRemove) {
Ed Tanous904063f2017-03-02 16:48:24 -080010360 $rootElement[i++] = newNode;
10361 for (var j = i, j2 = j + removeCount - 1,
10362 jj = $rootElement.length;
10363 j < jj; j++, j2++) {
10364 if (j2 < jj) {
10365 $rootElement[j] = $rootElement[j2];
10366 } else {
10367 delete $rootElement[j];
10368 }
10369 }
10370 $rootElement.length -= removeCount - 1;
10371
10372 // If the replaced element is also the jQuery .context then replace it
10373 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
10374 // http://api.jquery.com/context/
10375 if ($rootElement.context === firstElementToRemove) {
10376 $rootElement.context = newNode;
10377 }
10378 break;
10379 }
10380 }
10381 }
10382
10383 if (parent) {
10384 parent.replaceChild(newNode, firstElementToRemove);
10385 }
10386
10387 // Append all the `elementsToRemove` to a fragment. This will...
10388 // - remove them from the DOM
10389 // - allow them to still be traversed with .nextSibling
10390 // - allow a single fragment.qSA to fetch all elements being removed
10391 var fragment = window.document.createDocumentFragment();
10392 for (i = 0; i < removeCount; i++) {
10393 fragment.appendChild(elementsToRemove[i]);
10394 }
10395
10396 if (jqLite.hasData(firstElementToRemove)) {
10397 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
10398 // data here because there's no public interface in jQuery to do that and copying over
10399 // event listeners (which is the main use of private data) wouldn't work anyway.
10400 jqLite.data(newNode, jqLite.data(firstElementToRemove));
10401
10402 // Remove $destroy event listeners from `firstElementToRemove`
10403 jqLite(firstElementToRemove).off('$destroy');
10404 }
10405
10406 // Cleanup any data/listeners on the elements and children.
10407 // This includes invoking the $destroy event on any elements with listeners.
10408 jqLite.cleanData(fragment.querySelectorAll('*'));
10409
10410 // Update the jqLite collection to only contain the `newNode`
10411 for (i = 1; i < removeCount; i++) {
10412 delete elementsToRemove[i];
10413 }
10414 elementsToRemove[0] = newNode;
10415 elementsToRemove.length = 1;
10416 }
10417
10418
10419 function cloneAndAnnotateFn(fn, annotation) {
10420 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
10421 }
10422
10423
10424 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
10425 try {
10426 linkFn(scope, $element, attrs, controllers, transcludeFn);
10427 } catch (e) {
10428 $exceptionHandler(e, startingTag($element));
10429 }
10430 }
10431
10432
Ed Tanous4758d5b2017-06-06 15:28:13 -070010433 // Set up $watches for isolate scope and controller bindings.
Ed Tanous904063f2017-03-02 16:48:24 -080010434 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
10435 var removeWatchCollection = [];
10436 var initialChanges = {};
10437 var changes;
10438 forEach(bindings, function initializeBinding(definition, scopeName) {
10439 var attrName = definition.attrName,
10440 optional = definition.optional,
10441 mode = definition.mode, // @, =, <, or &
10442 lastValue,
10443 parentGet, parentSet, compare, removeWatch;
10444
10445 switch (mode) {
10446
10447 case '@':
10448 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010449 destination[scopeName] = attrs[attrName] = undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080010450 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070010451 removeWatch = attrs.$observe(attrName, function(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080010452 if (isString(value) || isBoolean(value)) {
10453 var oldValue = destination[scopeName];
10454 recordChanges(scopeName, value, oldValue);
10455 destination[scopeName] = value;
10456 }
10457 });
10458 attrs.$$observers[attrName].$$scope = scope;
10459 lastValue = attrs[attrName];
10460 if (isString(lastValue)) {
10461 // If the attribute has been provided then we trigger an interpolation to ensure
10462 // the value is there for use in the link fn
10463 destination[scopeName] = $interpolate(lastValue)(scope);
10464 } else if (isBoolean(lastValue)) {
10465 // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted
10466 // the value to boolean rather than a string, so we special case this situation
10467 destination[scopeName] = lastValue;
10468 }
10469 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
Ed Tanous4758d5b2017-06-06 15:28:13 -070010470 removeWatchCollection.push(removeWatch);
Ed Tanous904063f2017-03-02 16:48:24 -080010471 break;
10472
10473 case '=':
10474 if (!hasOwnProperty.call(attrs, attrName)) {
10475 if (optional) break;
Ed Tanous4758d5b2017-06-06 15:28:13 -070010476 attrs[attrName] = undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080010477 }
10478 if (optional && !attrs[attrName]) break;
10479
10480 parentGet = $parse(attrs[attrName]);
10481 if (parentGet.literal) {
10482 compare = equals;
10483 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010484 compare = simpleCompare;
Ed Tanous904063f2017-03-02 16:48:24 -080010485 }
10486 parentSet = parentGet.assign || function() {
10487 // reset the change, or we will throw this exception on every $digest
10488 lastValue = destination[scopeName] = parentGet(scope);
10489 throw $compileMinErr('nonassign',
Ed Tanous4758d5b2017-06-06 15:28:13 -070010490 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!',
Ed Tanous904063f2017-03-02 16:48:24 -080010491 attrs[attrName], attrName, directive.name);
10492 };
10493 lastValue = destination[scopeName] = parentGet(scope);
10494 var parentValueWatch = function parentValueWatch(parentValue) {
10495 if (!compare(parentValue, destination[scopeName])) {
10496 // we are out of sync and need to copy
10497 if (!compare(parentValue, lastValue)) {
10498 // parent changed and it has precedence
10499 destination[scopeName] = parentValue;
10500 } else {
10501 // if the parent can be assigned then do so
10502 parentSet(scope, parentValue = destination[scopeName]);
10503 }
10504 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070010505 lastValue = parentValue;
10506 return lastValue;
Ed Tanous904063f2017-03-02 16:48:24 -080010507 };
10508 parentValueWatch.$stateful = true;
10509 if (definition.collection) {
10510 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
10511 } else {
10512 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
10513 }
10514 removeWatchCollection.push(removeWatch);
10515 break;
10516
10517 case '<':
10518 if (!hasOwnProperty.call(attrs, attrName)) {
10519 if (optional) break;
Ed Tanous4758d5b2017-06-06 15:28:13 -070010520 attrs[attrName] = undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080010521 }
10522 if (optional && !attrs[attrName]) break;
10523
10524 parentGet = $parse(attrs[attrName]);
Ed Tanous4758d5b2017-06-06 15:28:13 -070010525 var deepWatch = parentGet.literal;
Ed Tanous904063f2017-03-02 16:48:24 -080010526
10527 var initialValue = destination[scopeName] = parentGet(scope);
10528 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
10529
10530 removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
10531 if (oldValue === newValue) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010532 if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) {
10533 return;
10534 }
Ed Tanous904063f2017-03-02 16:48:24 -080010535 oldValue = initialValue;
10536 }
10537 recordChanges(scopeName, newValue, oldValue);
10538 destination[scopeName] = newValue;
Ed Tanous4758d5b2017-06-06 15:28:13 -070010539 }, deepWatch);
Ed Tanous904063f2017-03-02 16:48:24 -080010540
10541 removeWatchCollection.push(removeWatch);
10542 break;
10543
10544 case '&':
10545 // Don't assign Object.prototype method to scope
10546 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
10547
10548 // Don't assign noop to destination if expression is not valid
10549 if (parentGet === noop && optional) break;
10550
10551 destination[scopeName] = function(locals) {
10552 return parentGet(scope, locals);
10553 };
10554 break;
10555 }
10556 });
10557
10558 function recordChanges(key, currentValue, previousValue) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010559 if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) {
Ed Tanous904063f2017-03-02 16:48:24 -080010560 // If we have not already scheduled the top level onChangesQueue handler then do so now
10561 if (!onChangesQueue) {
10562 scope.$$postDigest(flushOnChangesQueue);
10563 onChangesQueue = [];
10564 }
10565 // If we have not already queued a trigger of onChanges for this controller then do so now
10566 if (!changes) {
10567 changes = {};
10568 onChangesQueue.push(triggerOnChangesHook);
10569 }
10570 // If the has been a change on this property already then we need to reuse the previous value
10571 if (changes[key]) {
10572 previousValue = changes[key].previousValue;
10573 }
10574 // Store this change
10575 changes[key] = new SimpleChange(previousValue, currentValue);
10576 }
10577 }
10578
10579 function triggerOnChangesHook() {
10580 destination.$onChanges(changes);
10581 // Now clear the changes so that we schedule onChanges when more changes arrive
10582 changes = undefined;
10583 }
10584
10585 return {
10586 initialChanges: initialChanges,
10587 removeWatches: removeWatchCollection.length && function removeWatches() {
10588 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
10589 removeWatchCollection[i]();
10590 }
10591 }
10592 };
10593 }
10594 }];
10595}
10596
10597function SimpleChange(previous, current) {
10598 this.previousValue = previous;
10599 this.currentValue = current;
10600}
10601SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
10602
10603
Ed Tanous4758d5b2017-06-06 15:28:13 -070010604var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i;
10605var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;
10606
Ed Tanous904063f2017-03-02 16:48:24 -080010607/**
10608 * Converts all accepted directives format into proper directive name.
10609 * @param name Name to normalize
10610 */
10611function directiveNormalize(name) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010612 return name
10613 .replace(PREFIX_REGEXP, '')
10614 .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace);
Ed Tanous904063f2017-03-02 16:48:24 -080010615}
10616
10617/**
10618 * @ngdoc type
10619 * @name $compile.directive.Attributes
10620 *
10621 * @description
10622 * A shared object between directive compile / linking functions which contains normalized DOM
10623 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
10624 * needed since all of these are treated as equivalent in Angular:
10625 *
10626 * ```
10627 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
10628 * ```
10629 */
10630
10631/**
10632 * @ngdoc property
10633 * @name $compile.directive.Attributes#$attr
10634 *
10635 * @description
10636 * A map of DOM element attribute names to the normalized name. This is
10637 * needed to do reverse lookup from normalized name back to actual name.
10638 */
10639
10640
10641/**
10642 * @ngdoc method
10643 * @name $compile.directive.Attributes#$set
10644 * @kind function
10645 *
10646 * @description
10647 * Set DOM element attribute value.
10648 *
10649 *
10650 * @param {string} name Normalized element attribute name of the property to modify. The name is
10651 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
10652 * property to the original name.
10653 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
10654 */
10655
10656
10657
10658/**
10659 * Closure compiler type information
10660 */
10661
10662function nodesetLinkingFn(
10663 /* angular.Scope */ scope,
10664 /* NodeList */ nodeList,
10665 /* Element */ rootElement,
10666 /* function(Function) */ boundTranscludeFn
10667) {}
10668
10669function directiveLinkingFn(
10670 /* nodesetLinkingFn */ nodesetLinkingFn,
10671 /* angular.Scope */ scope,
10672 /* Node */ node,
10673 /* Element */ rootElement,
10674 /* function(Function) */ boundTranscludeFn
10675) {}
10676
10677function tokenDifference(str1, str2) {
10678 var values = '',
10679 tokens1 = str1.split(/\s+/),
10680 tokens2 = str2.split(/\s+/);
10681
10682 outer:
10683 for (var i = 0; i < tokens1.length; i++) {
10684 var token = tokens1[i];
10685 for (var j = 0; j < tokens2.length; j++) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070010686 if (token === tokens2[j]) continue outer;
Ed Tanous904063f2017-03-02 16:48:24 -080010687 }
10688 values += (values.length > 0 ? ' ' : '') + token;
10689 }
10690 return values;
10691}
10692
10693function removeComments(jqNodes) {
10694 jqNodes = jqLite(jqNodes);
10695 var i = jqNodes.length;
10696
10697 if (i <= 1) {
10698 return jqNodes;
10699 }
10700
10701 while (i--) {
10702 var node = jqNodes[i];
Ed Tanous4758d5b2017-06-06 15:28:13 -070010703 if (node.nodeType === NODE_TYPE_COMMENT ||
10704 (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) {
10705 splice.call(jqNodes, i, 1);
Ed Tanous904063f2017-03-02 16:48:24 -080010706 }
10707 }
10708 return jqNodes;
10709}
10710
10711var $controllerMinErr = minErr('$controller');
10712
10713
10714var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
10715function identifierForController(controller, ident) {
10716 if (ident && isString(ident)) return ident;
10717 if (isString(controller)) {
10718 var match = CNTRL_REG.exec(controller);
10719 if (match) return match[3];
10720 }
10721}
10722
10723
10724/**
10725 * @ngdoc provider
10726 * @name $controllerProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070010727 * @this
10728 *
Ed Tanous904063f2017-03-02 16:48:24 -080010729 * @description
10730 * The {@link ng.$controller $controller service} is used by Angular to create new
10731 * controllers.
10732 *
10733 * This provider allows controller registration via the
10734 * {@link ng.$controllerProvider#register register} method.
10735 */
10736function $ControllerProvider() {
10737 var controllers = {},
10738 globals = false;
10739
10740 /**
10741 * @ngdoc method
10742 * @name $controllerProvider#has
10743 * @param {string} name Controller name to check.
10744 */
10745 this.has = function(name) {
10746 return controllers.hasOwnProperty(name);
10747 };
10748
10749 /**
10750 * @ngdoc method
10751 * @name $controllerProvider#register
10752 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
10753 * the names and the values are the constructors.
10754 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
10755 * annotations in the array notation).
10756 */
10757 this.register = function(name, constructor) {
10758 assertNotHasOwnProperty(name, 'controller');
10759 if (isObject(name)) {
10760 extend(controllers, name);
10761 } else {
10762 controllers[name] = constructor;
10763 }
10764 };
10765
10766 /**
10767 * @ngdoc method
10768 * @name $controllerProvider#allowGlobals
10769 * @description If called, allows `$controller` to find controller constructors on `window`
Ed Tanous4758d5b2017-06-06 15:28:13 -070010770 *
10771 * @deprecated
10772 * sinceVersion="v1.3.0"
10773 * removeVersion="v1.7.0"
10774 * This method of finding controllers has been deprecated.
Ed Tanous904063f2017-03-02 16:48:24 -080010775 */
10776 this.allowGlobals = function() {
10777 globals = true;
10778 };
10779
10780
10781 this.$get = ['$injector', '$window', function($injector, $window) {
10782
10783 /**
10784 * @ngdoc service
10785 * @name $controller
10786 * @requires $injector
10787 *
10788 * @param {Function|string} constructor If called with a function then it's considered to be the
10789 * controller constructor function. Otherwise it's considered to be a string which is used
10790 * to retrieve the controller constructor using the following steps:
10791 *
10792 * * check if a controller with given name is registered via `$controllerProvider`
10793 * * check if evaluating the string on the current scope returns a constructor
10794 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
Ed Tanous4758d5b2017-06-06 15:28:13 -070010795 * `window` object (deprecated, not recommended)
Ed Tanous904063f2017-03-02 16:48:24 -080010796 *
10797 * The string can use the `controller as property` syntax, where the controller instance is published
10798 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
10799 * to work correctly.
10800 *
10801 * @param {Object} locals Injection locals for Controller.
10802 * @return {Object} Instance of given controller.
10803 *
10804 * @description
10805 * `$controller` service is responsible for instantiating controllers.
10806 *
10807 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
10808 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
10809 */
10810 return function $controller(expression, locals, later, ident) {
10811 // PRIVATE API:
10812 // param `later` --- indicates that the controller's constructor is invoked at a later time.
10813 // If true, $controller will allocate the object with the correct
10814 // prototype chain, but will not invoke the controller until a returned
10815 // callback is invoked.
10816 // param `ident` --- An optional label which overrides the label parsed from the controller
10817 // expression, if any.
10818 var instance, match, constructor, identifier;
10819 later = later === true;
10820 if (ident && isString(ident)) {
10821 identifier = ident;
10822 }
10823
10824 if (isString(expression)) {
10825 match = expression.match(CNTRL_REG);
10826 if (!match) {
10827 throw $controllerMinErr('ctrlfmt',
Ed Tanous4758d5b2017-06-06 15:28:13 -070010828 'Badly formed controller string \'{0}\'. ' +
10829 'Must match `__name__ as __id__` or `__name__`.', expression);
Ed Tanous904063f2017-03-02 16:48:24 -080010830 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070010831 constructor = match[1];
Ed Tanous904063f2017-03-02 16:48:24 -080010832 identifier = identifier || match[3];
10833 expression = controllers.hasOwnProperty(constructor)
10834 ? controllers[constructor]
10835 : getter(locals.$scope, constructor, true) ||
10836 (globals ? getter($window, constructor, true) : undefined);
10837
Ed Tanous4758d5b2017-06-06 15:28:13 -070010838 if (!expression) {
10839 throw $controllerMinErr('ctrlreg',
10840 'The controller with the name \'{0}\' is not registered.', constructor);
10841 }
10842
Ed Tanous904063f2017-03-02 16:48:24 -080010843 assertArgFn(expression, constructor, true);
10844 }
10845
10846 if (later) {
10847 // Instantiate controller later:
10848 // This machinery is used to create an instance of the object before calling the
10849 // controller's constructor itself.
10850 //
10851 // This allows properties to be added to the controller before the constructor is
10852 // invoked. Primarily, this is used for isolate scope bindings in $compile.
10853 //
10854 // This feature is not intended for use by applications, and is thus not documented
10855 // publicly.
10856 // Object creation: http://jsperf.com/create-constructor/2
10857 var controllerPrototype = (isArray(expression) ?
10858 expression[expression.length - 1] : expression).prototype;
10859 instance = Object.create(controllerPrototype || null);
10860
10861 if (identifier) {
10862 addIdentifier(locals, identifier, instance, constructor || expression.name);
10863 }
10864
Ed Tanous4758d5b2017-06-06 15:28:13 -070010865 return extend(function $controllerInit() {
Ed Tanous904063f2017-03-02 16:48:24 -080010866 var result = $injector.invoke(expression, instance, locals, constructor);
10867 if (result !== instance && (isObject(result) || isFunction(result))) {
10868 instance = result;
10869 if (identifier) {
10870 // If result changed, re-assign controllerAs value to scope.
10871 addIdentifier(locals, identifier, instance, constructor || expression.name);
10872 }
10873 }
10874 return instance;
10875 }, {
10876 instance: instance,
10877 identifier: identifier
10878 });
10879 }
10880
10881 instance = $injector.instantiate(expression, locals, constructor);
10882
10883 if (identifier) {
10884 addIdentifier(locals, identifier, instance, constructor || expression.name);
10885 }
10886
10887 return instance;
10888 };
10889
10890 function addIdentifier(locals, identifier, instance, name) {
10891 if (!(locals && isObject(locals.$scope))) {
10892 throw minErr('$controller')('noscp',
Ed Tanous4758d5b2017-06-06 15:28:13 -070010893 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.',
Ed Tanous904063f2017-03-02 16:48:24 -080010894 name, identifier);
10895 }
10896
10897 locals.$scope[identifier] = instance;
10898 }
10899 }];
10900}
10901
10902/**
10903 * @ngdoc service
10904 * @name $document
10905 * @requires $window
Ed Tanous4758d5b2017-06-06 15:28:13 -070010906 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080010907 *
10908 * @description
10909 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
10910 *
10911 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070010912 <example module="documentExample" name="document">
Ed Tanous904063f2017-03-02 16:48:24 -080010913 <file name="index.html">
10914 <div ng-controller="ExampleController">
10915 <p>$document title: <b ng-bind="title"></b></p>
10916 <p>window.document title: <b ng-bind="windowTitle"></b></p>
10917 </div>
10918 </file>
10919 <file name="script.js">
10920 angular.module('documentExample', [])
10921 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
10922 $scope.title = $document[0].title;
10923 $scope.windowTitle = angular.element(window.document)[0].title;
10924 }]);
10925 </file>
10926 </example>
10927 */
10928function $DocumentProvider() {
10929 this.$get = ['$window', function(window) {
10930 return jqLite(window.document);
10931 }];
10932}
10933
Ed Tanous4758d5b2017-06-06 15:28:13 -070010934
10935/**
10936 * @private
10937 * @this
10938 * Listens for document visibility change and makes the current status accessible.
10939 */
10940function $$IsDocumentHiddenProvider() {
10941 this.$get = ['$document', '$rootScope', function($document, $rootScope) {
10942 var doc = $document[0];
10943 var hidden = doc && doc.hidden;
10944
10945 $document.on('visibilitychange', changeListener);
10946
10947 $rootScope.$on('$destroy', function() {
10948 $document.off('visibilitychange', changeListener);
10949 });
10950
10951 function changeListener() {
10952 hidden = doc.hidden;
10953 }
10954
10955 return function() {
10956 return hidden;
10957 };
10958 }];
10959}
10960
Ed Tanous904063f2017-03-02 16:48:24 -080010961/**
10962 * @ngdoc service
10963 * @name $exceptionHandler
10964 * @requires ng.$log
Ed Tanous4758d5b2017-06-06 15:28:13 -070010965 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080010966 *
10967 * @description
10968 * Any uncaught exception in angular expressions is delegated to this service.
10969 * The default implementation simply delegates to `$log.error` which logs it into
10970 * the browser console.
10971 *
10972 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
10973 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
10974 *
10975 * ## Example:
10976 *
10977 * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
10978 * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
10979 * of `$log.error()`.
10980 *
10981 * ```js
10982 * angular.
10983 * module('exceptionOverwrite', []).
10984 * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
10985 * return function myExceptionHandler(exception, cause) {
10986 * logErrorsToBackend(exception, cause);
10987 * $log.warn(exception, cause);
10988 * };
10989 * }]);
10990 * ```
10991 *
10992 * <hr />
10993 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
10994 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
10995 * (unless executed during a digest).
10996 *
10997 * If you wish, you can manually delegate exceptions, e.g.
10998 * `try { ... } catch(e) { $exceptionHandler(e); }`
10999 *
11000 * @param {Error} exception Exception associated with the error.
11001 * @param {string=} cause Optional information about the context in which
11002 * the error was thrown.
11003 *
11004 */
11005function $ExceptionHandlerProvider() {
11006 this.$get = ['$log', function($log) {
11007 return function(exception, cause) {
11008 $log.error.apply($log, arguments);
11009 };
11010 }];
11011}
11012
Ed Tanous4758d5b2017-06-06 15:28:13 -070011013var $$ForceReflowProvider = /** @this */ function() {
Ed Tanous904063f2017-03-02 16:48:24 -080011014 this.$get = ['$document', function($document) {
11015 return function(domNode) {
11016 //the line below will force the browser to perform a repaint so
11017 //that all the animated elements within the animation frame will
11018 //be properly updated and drawn on screen. This is required to
11019 //ensure that the preparation animation is properly flushed so that
11020 //the active state picks up from there. DO NOT REMOVE THIS LINE.
11021 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
11022 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
11023 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
11024 if (domNode) {
11025 if (!domNode.nodeType && domNode instanceof jqLite) {
11026 domNode = domNode[0];
11027 }
11028 } else {
11029 domNode = $document[0].body;
11030 }
11031 return domNode.offsetWidth + 1;
11032 };
11033 }];
11034};
11035
11036var APPLICATION_JSON = 'application/json';
11037var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
11038var JSON_START = /^\[|^\{(?!\{)/;
11039var JSON_ENDS = {
11040 '[': /]$/,
11041 '{': /}$/
11042};
Ed Tanous4758d5b2017-06-06 15:28:13 -070011043var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
Ed Tanous904063f2017-03-02 16:48:24 -080011044var $httpMinErr = minErr('$http');
Ed Tanous904063f2017-03-02 16:48:24 -080011045
11046function serializeValue(v) {
11047 if (isObject(v)) {
11048 return isDate(v) ? v.toISOString() : toJson(v);
11049 }
11050 return v;
11051}
11052
11053
Ed Tanous4758d5b2017-06-06 15:28:13 -070011054/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080011055function $HttpParamSerializerProvider() {
11056 /**
11057 * @ngdoc service
11058 * @name $httpParamSerializer
11059 * @description
11060 *
11061 * Default {@link $http `$http`} params serializer that converts objects to strings
11062 * according to the following rules:
11063 *
11064 * * `{'foo': 'bar'}` results in `foo=bar`
11065 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
11066 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
11067 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
11068 *
11069 * Note that serializer will sort the request parameters alphabetically.
11070 * */
11071
11072 this.$get = function() {
11073 return function ngParamSerializer(params) {
11074 if (!params) return '';
11075 var parts = [];
11076 forEachSorted(params, function(value, key) {
11077 if (value === null || isUndefined(value)) return;
11078 if (isArray(value)) {
11079 forEach(value, function(v) {
11080 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
11081 });
11082 } else {
11083 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
11084 }
11085 });
11086
11087 return parts.join('&');
11088 };
11089 };
11090}
11091
Ed Tanous4758d5b2017-06-06 15:28:13 -070011092/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080011093function $HttpParamSerializerJQLikeProvider() {
11094 /**
11095 * @ngdoc service
11096 * @name $httpParamSerializerJQLike
Ed Tanous4758d5b2017-06-06 15:28:13 -070011097 *
Ed Tanous904063f2017-03-02 16:48:24 -080011098 * @description
11099 *
11100 * Alternative {@link $http `$http`} params serializer that follows
11101 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
11102 * The serializer will also sort the params alphabetically.
11103 *
11104 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
11105 *
11106 * ```js
11107 * $http({
11108 * url: myUrl,
11109 * method: 'GET',
11110 * params: myParams,
11111 * paramSerializer: '$httpParamSerializerJQLike'
11112 * });
11113 * ```
11114 *
11115 * It is also possible to set it as the default `paramSerializer` in the
11116 * {@link $httpProvider#defaults `$httpProvider`}.
11117 *
11118 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
11119 * form data for submission:
11120 *
11121 * ```js
11122 * .controller(function($http, $httpParamSerializerJQLike) {
11123 * //...
11124 *
11125 * $http({
11126 * url: myUrl,
11127 * method: 'POST',
11128 * data: $httpParamSerializerJQLike(myData),
11129 * headers: {
11130 * 'Content-Type': 'application/x-www-form-urlencoded'
11131 * }
11132 * });
11133 *
11134 * });
11135 * ```
11136 *
11137 * */
11138 this.$get = function() {
11139 return function jQueryLikeParamSerializer(params) {
11140 if (!params) return '';
11141 var parts = [];
11142 serialize(params, '', true);
11143 return parts.join('&');
11144
11145 function serialize(toSerialize, prefix, topLevel) {
11146 if (toSerialize === null || isUndefined(toSerialize)) return;
11147 if (isArray(toSerialize)) {
11148 forEach(toSerialize, function(value, index) {
11149 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
11150 });
11151 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
11152 forEachSorted(toSerialize, function(value, key) {
11153 serialize(value, prefix +
11154 (topLevel ? '' : '[') +
11155 key +
11156 (topLevel ? '' : ']'));
11157 });
11158 } else {
11159 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
11160 }
11161 }
11162 };
11163 };
11164}
11165
11166function defaultHttpResponseTransform(data, headers) {
11167 if (isString(data)) {
11168 // Strip json vulnerability protection prefix and trim whitespace
11169 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
11170
11171 if (tempData) {
11172 var contentType = headers('Content-Type');
11173 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070011174 try {
11175 data = fromJson(tempData);
11176 } catch (e) {
11177 throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' +
11178 'Parse error: "{1}"', data, e);
11179 }
Ed Tanous904063f2017-03-02 16:48:24 -080011180 }
11181 }
11182 }
11183
11184 return data;
11185}
11186
11187function isJsonLike(str) {
11188 var jsonStart = str.match(JSON_START);
11189 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
11190}
11191
11192/**
11193 * Parse headers into key value object
11194 *
11195 * @param {string} headers Raw headers as a string
11196 * @returns {Object} Parsed headers as key value object
11197 */
11198function parseHeaders(headers) {
11199 var parsed = createMap(), i;
11200
11201 function fillInParsed(key, val) {
11202 if (key) {
11203 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
11204 }
11205 }
11206
11207 if (isString(headers)) {
11208 forEach(headers.split('\n'), function(line) {
11209 i = line.indexOf(':');
11210 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
11211 });
11212 } else if (isObject(headers)) {
11213 forEach(headers, function(headerVal, headerKey) {
11214 fillInParsed(lowercase(headerKey), trim(headerVal));
11215 });
11216 }
11217
11218 return parsed;
11219}
11220
11221
11222/**
11223 * Returns a function that provides access to parsed headers.
11224 *
11225 * Headers are lazy parsed when first requested.
11226 * @see parseHeaders
11227 *
11228 * @param {(string|Object)} headers Headers to provide access to.
11229 * @returns {function(string=)} Returns a getter function which if called with:
11230 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070011231 * - if called with an argument returns a single header value or null
Ed Tanous904063f2017-03-02 16:48:24 -080011232 * - if called with no arguments returns an object containing all headers.
11233 */
11234function headersGetter(headers) {
11235 var headersObj;
11236
11237 return function(name) {
11238 if (!headersObj) headersObj = parseHeaders(headers);
11239
11240 if (name) {
11241 var value = headersObj[lowercase(name)];
Ed Tanous4758d5b2017-06-06 15:28:13 -070011242 if (value === undefined) {
Ed Tanous904063f2017-03-02 16:48:24 -080011243 value = null;
11244 }
11245 return value;
11246 }
11247
11248 return headersObj;
11249 };
11250}
11251
11252
11253/**
11254 * Chain all given functions
11255 *
11256 * This function is used for both request and response transforming
11257 *
11258 * @param {*} data Data to transform.
11259 * @param {function(string=)} headers HTTP headers getter fn.
11260 * @param {number} status HTTP status code of the response.
11261 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
11262 * @returns {*} Transformed data.
11263 */
11264function transformData(data, headers, status, fns) {
11265 if (isFunction(fns)) {
11266 return fns(data, headers, status);
11267 }
11268
11269 forEach(fns, function(fn) {
11270 data = fn(data, headers, status);
11271 });
11272
11273 return data;
11274}
11275
11276
11277function isSuccess(status) {
11278 return 200 <= status && status < 300;
11279}
11280
11281
11282/**
11283 * @ngdoc provider
11284 * @name $httpProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070011285 * @this
11286 *
Ed Tanous904063f2017-03-02 16:48:24 -080011287 * @description
11288 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
11289 * */
11290function $HttpProvider() {
11291 /**
11292 * @ngdoc property
11293 * @name $httpProvider#defaults
11294 * @description
11295 *
11296 * Object containing default values for all {@link ng.$http $http} requests.
11297 *
11298 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
11299 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
11300 * by default. See {@link $http#caching $http Caching} for more information.
11301 *
11302 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
11303 * Defaults value is `'XSRF-TOKEN'`.
11304 *
11305 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
11306 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
11307 *
11308 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
11309 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
11310 * setting default headers.
11311 * - **`defaults.headers.common`**
11312 * - **`defaults.headers.post`**
11313 * - **`defaults.headers.put`**
11314 * - **`defaults.headers.patch`**
11315 *
11316 *
11317 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
11318 * used to the prepare string representation of request parameters (specified as an object).
11319 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
11320 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
11321 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070011322 * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the
11323 * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
11324 * {@link $jsonpCallbacks} service. Defaults to `'callback'`.
11325 *
Ed Tanous904063f2017-03-02 16:48:24 -080011326 **/
11327 var defaults = this.defaults = {
11328 // transform incoming response data
11329 transformResponse: [defaultHttpResponseTransform],
11330
11331 // transform outgoing request data
11332 transformRequest: [function(d) {
11333 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
11334 }],
11335
11336 // default headers
11337 headers: {
11338 common: {
11339 'Accept': 'application/json, text/plain, */*'
11340 },
11341 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
11342 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
11343 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
11344 },
11345
11346 xsrfCookieName: 'XSRF-TOKEN',
11347 xsrfHeaderName: 'X-XSRF-TOKEN',
11348
Ed Tanous4758d5b2017-06-06 15:28:13 -070011349 paramSerializer: '$httpParamSerializer',
11350
11351 jsonpCallbackParam: 'callback'
Ed Tanous904063f2017-03-02 16:48:24 -080011352 };
11353
11354 var useApplyAsync = false;
11355 /**
11356 * @ngdoc method
11357 * @name $httpProvider#useApplyAsync
11358 * @description
11359 *
11360 * Configure $http service to combine processing of multiple http responses received at around
11361 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
11362 * significant performance improvement for bigger applications that make many HTTP requests
11363 * concurrently (common during application bootstrap).
11364 *
11365 * Defaults to false. If no value is specified, returns the current configured value.
11366 *
11367 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
11368 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
11369 * to load and share the same digest cycle.
11370 *
11371 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
11372 * otherwise, returns the current configured value.
11373 **/
11374 this.useApplyAsync = function(value) {
11375 if (isDefined(value)) {
11376 useApplyAsync = !!value;
11377 return this;
11378 }
11379 return useApplyAsync;
11380 };
11381
Ed Tanous904063f2017-03-02 16:48:24 -080011382 /**
11383 * @ngdoc property
11384 * @name $httpProvider#interceptors
11385 * @description
11386 *
11387 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
11388 * pre-processing of request or postprocessing of responses.
11389 *
11390 * These service factories are ordered by request, i.e. they are applied in the same order as the
11391 * array, on request, but reverse order, on response.
11392 *
11393 * {@link ng.$http#interceptors Interceptors detailed info}
11394 **/
11395 var interceptorFactories = this.interceptors = [];
11396
Ed Tanous4758d5b2017-06-06 15:28:13 -070011397 this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
11398 function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {
Ed Tanous904063f2017-03-02 16:48:24 -080011399
11400 var defaultCache = $cacheFactory('$http');
11401
11402 /**
11403 * Make sure that default param serializer is exposed as a function
11404 */
11405 defaults.paramSerializer = isString(defaults.paramSerializer) ?
11406 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
11407
11408 /**
11409 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
11410 * The reversal is needed so that we can build up the interception chain around the
11411 * server request.
11412 */
11413 var reversedInterceptors = [];
11414
11415 forEach(interceptorFactories, function(interceptorFactory) {
11416 reversedInterceptors.unshift(isString(interceptorFactory)
11417 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
11418 });
11419
11420 /**
11421 * @ngdoc service
11422 * @kind function
11423 * @name $http
11424 * @requires ng.$httpBackend
11425 * @requires $cacheFactory
11426 * @requires $rootScope
11427 * @requires $q
11428 * @requires $injector
11429 *
11430 * @description
11431 * The `$http` service is a core Angular service that facilitates communication with the remote
11432 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
11433 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
11434 *
11435 * For unit testing applications that use `$http` service, see
11436 * {@link ngMock.$httpBackend $httpBackend mock}.
11437 *
11438 * For a higher level of abstraction, please check out the {@link ngResource.$resource
11439 * $resource} service.
11440 *
11441 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
11442 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
11443 * it is important to familiarize yourself with these APIs and the guarantees they provide.
11444 *
11445 *
11446 * ## General usage
11447 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
11448 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
11449 *
11450 * ```js
11451 * // Simple GET request example:
11452 * $http({
11453 * method: 'GET',
11454 * url: '/someUrl'
11455 * }).then(function successCallback(response) {
11456 * // this callback will be called asynchronously
11457 * // when the response is available
11458 * }, function errorCallback(response) {
11459 * // called asynchronously if an error occurs
11460 * // or server returns response with an error status.
11461 * });
11462 * ```
11463 *
11464 * The response object has these properties:
11465 *
11466 * - **data** – `{string|Object}` – The response body transformed with the transform
11467 * functions.
11468 * - **status** – `{number}` – HTTP status code of the response.
11469 * - **headers** – `{function([headerName])}` – Header getter function.
11470 * - **config** – `{Object}` – The configuration object that was used to generate the request.
11471 * - **statusText** – `{string}` – HTTP status text of the response.
11472 *
11473 * A response status code between 200 and 299 is considered a success status and will result in
11474 * the success callback being called. Any response status code outside of that range is
11475 * considered an error status and will result in the error callback being called.
11476 * Also, status codes less than -1 are normalized to zero. -1 usually means the request was
11477 * aborted, e.g. using a `config.timeout`.
11478 * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning
11479 * that the outcome (success or error) will be determined by the final response status code.
11480 *
11481 *
11482 * ## Shortcut methods
11483 *
11484 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
11485 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
11486 * last argument.
11487 *
11488 * ```js
11489 * $http.get('/someUrl', config).then(successCallback, errorCallback);
11490 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
11491 * ```
11492 *
11493 * Complete list of shortcut methods:
11494 *
11495 * - {@link ng.$http#get $http.get}
11496 * - {@link ng.$http#head $http.head}
11497 * - {@link ng.$http#post $http.post}
11498 * - {@link ng.$http#put $http.put}
11499 * - {@link ng.$http#delete $http.delete}
11500 * - {@link ng.$http#jsonp $http.jsonp}
11501 * - {@link ng.$http#patch $http.patch}
11502 *
11503 *
11504 * ## Writing Unit Tests that use $http
11505 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
11506 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
11507 * request using trained responses.
11508 *
11509 * ```
11510 * $httpBackend.expectGET(...);
11511 * $http.get(...);
11512 * $httpBackend.flush();
11513 * ```
11514 *
Ed Tanous904063f2017-03-02 16:48:24 -080011515 * ## Setting HTTP Headers
11516 *
11517 * The $http service will automatically add certain HTTP headers to all requests. These defaults
11518 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
11519 * object, which currently contains this default configuration:
11520 *
11521 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
Ed Tanous4758d5b2017-06-06 15:28:13 -070011522 * - <code>Accept: application/json, text/plain, \*&#65279;/&#65279;\*</code>
Ed Tanous904063f2017-03-02 16:48:24 -080011523 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
11524 * - `Content-Type: application/json`
11525 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
11526 * - `Content-Type: application/json`
11527 *
11528 * To add or overwrite these defaults, simply add or remove a property from these configuration
11529 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
11530 * with the lowercased HTTP method name as the key, e.g.
11531 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
11532 *
11533 * The defaults can also be set at runtime via the `$http.defaults` object in the same
11534 * fashion. For example:
11535 *
11536 * ```
11537 * module.run(function($http) {
11538 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
11539 * });
11540 * ```
11541 *
11542 * In addition, you can supply a `headers` property in the config object passed when
11543 * calling `$http(config)`, which overrides the defaults without changing them globally.
11544 *
11545 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
11546 * Use the `headers` property, setting the desired header to `undefined`. For example:
11547 *
11548 * ```js
11549 * var req = {
11550 * method: 'POST',
11551 * url: 'http://example.com',
11552 * headers: {
11553 * 'Content-Type': undefined
11554 * },
11555 * data: { test: 'test' }
11556 * }
11557 *
11558 * $http(req).then(function(){...}, function(){...});
11559 * ```
11560 *
11561 * ## Transforming Requests and Responses
11562 *
11563 * Both requests and responses can be transformed using transformation functions: `transformRequest`
11564 * and `transformResponse`. These properties can be a single function that returns
11565 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
11566 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
11567 *
11568 * <div class="alert alert-warning">
11569 * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline.
11570 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
11571 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
11572 * function will be reflected on the scope and in any templates where the object is data-bound.
11573 * To prevent this, transform functions should have no side-effects.
11574 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
11575 * </div>
11576 *
11577 * ### Default Transformations
11578 *
11579 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
11580 * `defaults.transformResponse` properties. If a request does not provide its own transformations
11581 * then these will be applied.
11582 *
11583 * You can augment or replace the default transformations by modifying these properties by adding to or
11584 * replacing the array.
11585 *
11586 * Angular provides the following default transformations:
11587 *
11588 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
11589 *
11590 * - If the `data` property of the request configuration object contains an object, serialize it
11591 * into JSON format.
11592 *
11593 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
11594 *
11595 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
11596 * - If JSON response is detected, deserialize it using a JSON parser.
11597 *
11598 *
11599 * ### Overriding the Default Transformations Per Request
11600 *
11601 * If you wish to override the request/response transformations only for a single request then provide
11602 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
11603 * into `$http`.
11604 *
11605 * Note that if you provide these properties on the config object the default transformations will be
11606 * overwritten. If you wish to augment the default transformations then you must include them in your
11607 * local transformation array.
11608 *
11609 * The following code demonstrates adding a new response transformation to be run after the default response
11610 * transformations have been run.
11611 *
11612 * ```js
11613 * function appendTransform(defaults, transform) {
11614 *
11615 * // We can't guarantee that the default transformation is an array
11616 * defaults = angular.isArray(defaults) ? defaults : [defaults];
11617 *
11618 * // Append the new transformation to the defaults
11619 * return defaults.concat(transform);
11620 * }
11621 *
11622 * $http({
11623 * url: '...',
11624 * method: 'GET',
11625 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
11626 * return doTransform(value);
11627 * })
11628 * });
11629 * ```
11630 *
11631 *
11632 * ## Caching
11633 *
11634 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must
11635 * set the config.cache value or the default cache value to TRUE or to a cache object (created
11636 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes
11637 * precedence over the default cache value.
11638 *
11639 * In order to:
11640 * * cache all responses - set the default cache value to TRUE or to a cache object
11641 * * cache a specific response - set config.cache value to TRUE or to a cache object
11642 *
11643 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
11644 * then the default `$cacheFactory("$http")` object is used.
11645 *
11646 * The default cache value can be set by updating the
11647 * {@link ng.$http#defaults `$http.defaults.cache`} property or the
11648 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property.
11649 *
11650 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using
11651 * the relevant cache object. The next time the same request is made, the response is returned
11652 * from the cache without sending a request to the server.
11653 *
11654 * Take note that:
11655 *
11656 * * Only GET and JSONP requests are cached.
11657 * * The cache key is the request URL including search parameters; headers are not considered.
11658 * * Cached responses are returned asynchronously, in the same way as responses from the server.
11659 * * If multiple identical requests are made using the same cache, which is not yet populated,
11660 * one request will be made to the server and remaining requests will return the same response.
11661 * * A cache-control header on the response does not affect if or how responses are cached.
11662 *
11663 *
11664 * ## Interceptors
11665 *
11666 * Before you start creating interceptors, be sure to understand the
11667 * {@link ng.$q $q and deferred/promise APIs}.
11668 *
11669 * For purposes of global error handling, authentication, or any kind of synchronous or
11670 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
11671 * able to intercept requests before they are handed to the server and
11672 * responses before they are handed over to the application code that
11673 * initiated these requests. The interceptors leverage the {@link ng.$q
11674 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
11675 *
11676 * The interceptors are service factories that are registered with the `$httpProvider` by
11677 * adding them to the `$httpProvider.interceptors` array. The factory is called and
11678 * injected with dependencies (if specified) and returns the interceptor.
11679 *
11680 * There are two kinds of interceptors (and two kinds of rejection interceptors):
11681 *
11682 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
11683 * modify the `config` object or create a new one. The function needs to return the `config`
11684 * object directly, or a promise containing the `config` or a new `config` object.
11685 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
11686 * resolved with a rejection.
11687 * * `response`: interceptors get called with http `response` object. The function is free to
11688 * modify the `response` object or create a new one. The function needs to return the `response`
11689 * object directly, or as a promise containing the `response` or a new `response` object.
11690 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
11691 * resolved with a rejection.
11692 *
11693 *
11694 * ```js
11695 * // register the interceptor as a service
11696 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
11697 * return {
11698 * // optional method
11699 * 'request': function(config) {
11700 * // do something on success
11701 * return config;
11702 * },
11703 *
11704 * // optional method
11705 * 'requestError': function(rejection) {
11706 * // do something on error
11707 * if (canRecover(rejection)) {
11708 * return responseOrNewPromise
11709 * }
11710 * return $q.reject(rejection);
11711 * },
11712 *
11713 *
11714 *
11715 * // optional method
11716 * 'response': function(response) {
11717 * // do something on success
11718 * return response;
11719 * },
11720 *
11721 * // optional method
11722 * 'responseError': function(rejection) {
11723 * // do something on error
11724 * if (canRecover(rejection)) {
11725 * return responseOrNewPromise
11726 * }
11727 * return $q.reject(rejection);
11728 * }
11729 * };
11730 * });
11731 *
11732 * $httpProvider.interceptors.push('myHttpInterceptor');
11733 *
11734 *
11735 * // alternatively, register the interceptor via an anonymous factory
11736 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
11737 * return {
11738 * 'request': function(config) {
11739 * // same as above
11740 * },
11741 *
11742 * 'response': function(response) {
11743 * // same as above
11744 * }
11745 * };
11746 * });
11747 * ```
11748 *
11749 * ## Security Considerations
11750 *
11751 * When designing web applications, consider security threats from:
11752 *
11753 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
11754 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
11755 *
11756 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
11757 * pre-configured with strategies that address these issues, but for this to work backend server
11758 * cooperation is required.
11759 *
11760 * ### JSON Vulnerability Protection
11761 *
11762 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
11763 * allows third party website to turn your JSON resource URL into
11764 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
11765 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
11766 * Angular will automatically strip the prefix before processing it as JSON.
11767 *
11768 * For example if your server needs to return:
11769 * ```js
11770 * ['one','two']
11771 * ```
11772 *
11773 * which is vulnerable to attack, your server can return:
11774 * ```js
11775 * )]}',
11776 * ['one','two']
11777 * ```
11778 *
11779 * Angular will strip the prefix, before processing the JSON.
11780 *
11781 *
11782 * ### Cross Site Request Forgery (XSRF) Protection
11783 *
11784 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
11785 * which the attacker can trick an authenticated user into unknowingly executing actions on your
11786 * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the
11787 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
11788 * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the
11789 * cookie, your server can be assured that the XHR came from JavaScript running on your domain.
11790 * The header will not be set for cross-domain requests.
11791 *
11792 * To take advantage of this, your server needs to set a token in a JavaScript readable session
11793 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
11794 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
11795 * that only JavaScript running on your domain could have sent the request. The token must be
11796 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
11797 * making up its own tokens). We recommend that the token is a digest of your site's
11798 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
11799 * for added security.
11800 *
11801 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
11802 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
11803 * or the per-request config object.
11804 *
11805 * In order to prevent collisions in environments where multiple Angular apps share the
11806 * same domain or subdomain, we recommend that each application uses unique cookie name.
11807 *
11808 * @param {object} config Object describing the request to be made and how it should be
11809 * processed. The object has following properties:
11810 *
11811 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
Ed Tanous4758d5b2017-06-06 15:28:13 -070011812 * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested;
11813 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
Ed Tanous904063f2017-03-02 16:48:24 -080011814 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
11815 * with the `paramSerializer` and appended as GET parameters.
11816 * - **data** – `{string|Object}` – Data to be sent as the request message data.
11817 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
11818 * HTTP headers to send to the server. If the return value of a function is null, the
11819 * header will not be sent. Functions accept a config object as an argument.
11820 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
11821 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
11822 * The handler will be called in the context of a `$apply` block.
11823 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
11824 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
11825 * The handler will be called in the context of a `$apply` block.
11826 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
11827 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
11828 * - **transformRequest** –
11829 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
11830 * transform function or an array of such functions. The transform function takes the http
11831 * request body and headers and returns its transformed (typically serialized) version.
11832 * See {@link ng.$http#overriding-the-default-transformations-per-request
11833 * Overriding the Default Transformations}
11834 * - **transformResponse** –
11835 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
11836 * transform function or an array of such functions. The transform function takes the http
11837 * response body, headers and status and returns its transformed (typically deserialized) version.
11838 * See {@link ng.$http#overriding-the-default-transformations-per-request
11839 * Overriding the Default Transformations}
11840 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
11841 * prepare the string representation of request parameters (specified as an object).
11842 * If specified as string, it is interpreted as function registered with the
11843 * {@link $injector $injector}, which means you can create your own serializer
11844 * by registering it as a {@link auto.$provide#service service}.
11845 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
11846 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
11847 * - **cache** – `{boolean|Object}` – A boolean value or object created with
11848 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
11849 * See {@link $http#caching $http Caching} for more information.
11850 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
11851 * that should abort the request when resolved.
11852 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
11853 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
11854 * for more information.
11855 * - **responseType** - `{string}` - see
11856 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
11857 *
11858 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
11859 * when the request succeeds or fails.
11860 *
11861 *
11862 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
11863 * requests. This is primarily meant to be used for debugging purposes.
11864 *
11865 *
11866 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070011867<example module="httpExample" name="http-service">
Ed Tanous904063f2017-03-02 16:48:24 -080011868<file name="index.html">
11869 <div ng-controller="FetchController">
11870 <select ng-model="method" aria-label="Request method">
11871 <option>GET</option>
11872 <option>JSONP</option>
11873 </select>
11874 <input type="text" ng-model="url" size="80" aria-label="URL" />
11875 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
11876 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
11877 <button id="samplejsonpbtn"
11878 ng-click="updateModel('JSONP',
Ed Tanous4758d5b2017-06-06 15:28:13 -070011879 'https://angularjs.org/greet.php?name=Super%20Hero')">
Ed Tanous904063f2017-03-02 16:48:24 -080011880 Sample JSONP
11881 </button>
11882 <button id="invalidjsonpbtn"
Ed Tanous4758d5b2017-06-06 15:28:13 -070011883 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')">
Ed Tanous904063f2017-03-02 16:48:24 -080011884 Invalid JSONP
11885 </button>
11886 <pre>http status code: {{status}}</pre>
11887 <pre>http response data: {{data}}</pre>
11888 </div>
11889</file>
11890<file name="script.js">
11891 angular.module('httpExample', [])
Ed Tanous4758d5b2017-06-06 15:28:13 -070011892 .config(['$sceDelegateProvider', function($sceDelegateProvider) {
11893 // We must whitelist the JSONP endpoint that we are using to show that we trust it
11894 $sceDelegateProvider.resourceUrlWhitelist([
11895 'self',
11896 'https://angularjs.org/**'
11897 ]);
11898 }])
Ed Tanous904063f2017-03-02 16:48:24 -080011899 .controller('FetchController', ['$scope', '$http', '$templateCache',
11900 function($scope, $http, $templateCache) {
11901 $scope.method = 'GET';
11902 $scope.url = 'http-hello.html';
11903
11904 $scope.fetch = function() {
11905 $scope.code = null;
11906 $scope.response = null;
11907
11908 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
11909 then(function(response) {
11910 $scope.status = response.status;
11911 $scope.data = response.data;
11912 }, function(response) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070011913 $scope.data = response.data || 'Request failed';
Ed Tanous904063f2017-03-02 16:48:24 -080011914 $scope.status = response.status;
11915 });
11916 };
11917
11918 $scope.updateModel = function(method, url) {
11919 $scope.method = method;
11920 $scope.url = url;
11921 };
11922 }]);
11923</file>
11924<file name="http-hello.html">
11925 Hello, $http!
11926</file>
11927<file name="protractor.js" type="protractor">
11928 var status = element(by.binding('status'));
11929 var data = element(by.binding('data'));
11930 var fetchBtn = element(by.id('fetchbtn'));
11931 var sampleGetBtn = element(by.id('samplegetbtn'));
Ed Tanous904063f2017-03-02 16:48:24 -080011932 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
11933
11934 it('should make an xhr GET request', function() {
11935 sampleGetBtn.click();
11936 fetchBtn.click();
11937 expect(status.getText()).toMatch('200');
11938 expect(data.getText()).toMatch(/Hello, \$http!/);
11939 });
11940
11941// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
11942// it('should make a JSONP request to angularjs.org', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070011943// var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
Ed Tanous904063f2017-03-02 16:48:24 -080011944// sampleJsonpBtn.click();
11945// fetchBtn.click();
11946// expect(status.getText()).toMatch('200');
11947// expect(data.getText()).toMatch(/Super Hero!/);
11948// });
11949
11950 it('should make JSONP request to invalid URL and invoke the error handler',
11951 function() {
11952 invalidJsonpBtn.click();
11953 fetchBtn.click();
11954 expect(status.getText()).toMatch('0');
11955 expect(data.getText()).toMatch('Request failed');
11956 });
11957</file>
11958</example>
11959 */
11960 function $http(requestConfig) {
11961
11962 if (!isObject(requestConfig)) {
11963 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
11964 }
11965
Ed Tanous4758d5b2017-06-06 15:28:13 -070011966 if (!isString($sce.valueOf(requestConfig.url))) {
11967 throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url);
Ed Tanous904063f2017-03-02 16:48:24 -080011968 }
11969
11970 var config = extend({
11971 method: 'get',
11972 transformRequest: defaults.transformRequest,
11973 transformResponse: defaults.transformResponse,
Ed Tanous4758d5b2017-06-06 15:28:13 -070011974 paramSerializer: defaults.paramSerializer,
11975 jsonpCallbackParam: defaults.jsonpCallbackParam
Ed Tanous904063f2017-03-02 16:48:24 -080011976 }, requestConfig);
11977
11978 config.headers = mergeHeaders(requestConfig);
11979 config.method = uppercase(config.method);
11980 config.paramSerializer = isString(config.paramSerializer) ?
11981 $injector.get(config.paramSerializer) : config.paramSerializer;
11982
Ed Tanous4758d5b2017-06-06 15:28:13 -070011983 $browser.$$incOutstandingRequestCount();
11984
Ed Tanous904063f2017-03-02 16:48:24 -080011985 var requestInterceptors = [];
11986 var responseInterceptors = [];
Ed Tanous4758d5b2017-06-06 15:28:13 -070011987 var promise = $q.resolve(config);
Ed Tanous904063f2017-03-02 16:48:24 -080011988
11989 // apply interceptors
11990 forEach(reversedInterceptors, function(interceptor) {
11991 if (interceptor.request || interceptor.requestError) {
11992 requestInterceptors.unshift(interceptor.request, interceptor.requestError);
11993 }
11994 if (interceptor.response || interceptor.responseError) {
11995 responseInterceptors.push(interceptor.response, interceptor.responseError);
11996 }
11997 });
11998
11999 promise = chainInterceptors(promise, requestInterceptors);
12000 promise = promise.then(serverRequest);
12001 promise = chainInterceptors(promise, responseInterceptors);
Ed Tanous4758d5b2017-06-06 15:28:13 -070012002 promise = promise.finally(completeOutstandingRequest);
Ed Tanous904063f2017-03-02 16:48:24 -080012003
12004 return promise;
12005
12006
12007 function chainInterceptors(promise, interceptors) {
12008 for (var i = 0, ii = interceptors.length; i < ii;) {
12009 var thenFn = interceptors[i++];
12010 var rejectFn = interceptors[i++];
12011
12012 promise = promise.then(thenFn, rejectFn);
12013 }
12014
12015 interceptors.length = 0;
12016
12017 return promise;
12018 }
12019
Ed Tanous4758d5b2017-06-06 15:28:13 -070012020 function completeOutstandingRequest() {
12021 $browser.$$completeOutstandingRequest(noop);
12022 }
12023
Ed Tanous904063f2017-03-02 16:48:24 -080012024 function executeHeaderFns(headers, config) {
12025 var headerContent, processedHeaders = {};
12026
12027 forEach(headers, function(headerFn, header) {
12028 if (isFunction(headerFn)) {
12029 headerContent = headerFn(config);
12030 if (headerContent != null) {
12031 processedHeaders[header] = headerContent;
12032 }
12033 } else {
12034 processedHeaders[header] = headerFn;
12035 }
12036 });
12037
12038 return processedHeaders;
12039 }
12040
12041 function mergeHeaders(config) {
12042 var defHeaders = defaults.headers,
12043 reqHeaders = extend({}, config.headers),
12044 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
12045
12046 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
12047
12048 // using for-in instead of forEach to avoid unnecessary iteration after header has been found
12049 defaultHeadersIteration:
12050 for (defHeaderName in defHeaders) {
12051 lowercaseDefHeaderName = lowercase(defHeaderName);
12052
12053 for (reqHeaderName in reqHeaders) {
12054 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
12055 continue defaultHeadersIteration;
12056 }
12057 }
12058
12059 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
12060 }
12061
12062 // execute if header value is a function for merged headers
12063 return executeHeaderFns(reqHeaders, shallowCopy(config));
12064 }
12065
12066 function serverRequest(config) {
12067 var headers = config.headers;
12068 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
12069
12070 // strip content-type if data is undefined
12071 if (isUndefined(reqData)) {
12072 forEach(headers, function(value, header) {
12073 if (lowercase(header) === 'content-type') {
12074 delete headers[header];
12075 }
12076 });
12077 }
12078
12079 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
12080 config.withCredentials = defaults.withCredentials;
12081 }
12082
12083 // send request
12084 return sendReq(config, reqData).then(transformResponse, transformResponse);
12085 }
12086
12087 function transformResponse(response) {
12088 // make a copy since the response must be cacheable
12089 var resp = extend({}, response);
12090 resp.data = transformData(response.data, response.headers, response.status,
12091 config.transformResponse);
12092 return (isSuccess(response.status))
12093 ? resp
12094 : $q.reject(resp);
12095 }
12096 }
12097
12098 $http.pendingRequests = [];
12099
12100 /**
12101 * @ngdoc method
12102 * @name $http#get
12103 *
12104 * @description
12105 * Shortcut method to perform `GET` request.
12106 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070012107 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12108 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
Ed Tanous904063f2017-03-02 16:48:24 -080012109 * @param {Object=} config Optional configuration object
12110 * @returns {HttpPromise} Future object
12111 */
12112
12113 /**
12114 * @ngdoc method
12115 * @name $http#delete
12116 *
12117 * @description
12118 * Shortcut method to perform `DELETE` request.
12119 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070012120 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12121 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
Ed Tanous904063f2017-03-02 16:48:24 -080012122 * @param {Object=} config Optional configuration object
12123 * @returns {HttpPromise} Future object
12124 */
12125
12126 /**
12127 * @ngdoc method
12128 * @name $http#head
12129 *
12130 * @description
12131 * Shortcut method to perform `HEAD` request.
12132 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070012133 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12134 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
Ed Tanous904063f2017-03-02 16:48:24 -080012135 * @param {Object=} config Optional configuration object
12136 * @returns {HttpPromise} Future object
12137 */
12138
12139 /**
12140 * @ngdoc method
12141 * @name $http#jsonp
12142 *
12143 * @description
12144 * Shortcut method to perform `JSONP` request.
Ed Tanous4758d5b2017-06-06 15:28:13 -070012145 *
12146 * Note that, since JSONP requests are sensitive because the response is given full access to the browser,
12147 * the url must be declared, via {@link $sce} as a trusted resource URL.
12148 * You can trust a URL by adding it to the whitelist via
12149 * {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or
12150 * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}.
12151 *
12152 * JSONP requests must specify a callback to be used in the response from the server. This callback
12153 * is passed as a query parameter in the request. You must specify the name of this parameter by
12154 * setting the `jsonpCallbackParam` property on the request config object.
12155 *
12156 * ```
12157 * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'})
12158 * ```
12159 *
12160 * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`.
12161 * Initially this is set to `'callback'`.
12162 *
12163 * <div class="alert alert-danger">
12164 * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback
12165 * parameter value should go.
12166 * </div>
12167 *
Ed Tanous904063f2017-03-02 16:48:24 -080012168 * If you would like to customise where and how the callbacks are stored then try overriding
12169 * or decorating the {@link $jsonpCallbacks} service.
12170 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070012171 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12172 * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
Ed Tanous904063f2017-03-02 16:48:24 -080012173 * @param {Object=} config Optional configuration object
12174 * @returns {HttpPromise} Future object
12175 */
12176 createShortMethods('get', 'delete', 'head', 'jsonp');
12177
12178 /**
12179 * @ngdoc method
12180 * @name $http#post
12181 *
12182 * @description
12183 * Shortcut method to perform `POST` request.
12184 *
12185 * @param {string} url Relative or absolute URL specifying the destination of the request
12186 * @param {*} data Request content
12187 * @param {Object=} config Optional configuration object
12188 * @returns {HttpPromise} Future object
12189 */
12190
12191 /**
12192 * @ngdoc method
12193 * @name $http#put
12194 *
12195 * @description
12196 * Shortcut method to perform `PUT` request.
12197 *
12198 * @param {string} url Relative or absolute URL specifying the destination of the request
12199 * @param {*} data Request content
12200 * @param {Object=} config Optional configuration object
12201 * @returns {HttpPromise} Future object
12202 */
12203
12204 /**
12205 * @ngdoc method
12206 * @name $http#patch
12207 *
12208 * @description
12209 * Shortcut method to perform `PATCH` request.
12210 *
12211 * @param {string} url Relative or absolute URL specifying the destination of the request
12212 * @param {*} data Request content
12213 * @param {Object=} config Optional configuration object
12214 * @returns {HttpPromise} Future object
12215 */
12216 createShortMethodsWithData('post', 'put', 'patch');
12217
12218 /**
12219 * @ngdoc property
12220 * @name $http#defaults
12221 *
12222 * @description
12223 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
12224 * default headers, withCredentials as well as request and response transformations.
12225 *
12226 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
12227 */
12228 $http.defaults = defaults;
12229
12230
12231 return $http;
12232
12233
12234 function createShortMethods(names) {
12235 forEach(arguments, function(name) {
12236 $http[name] = function(url, config) {
12237 return $http(extend({}, config || {}, {
12238 method: name,
12239 url: url
12240 }));
12241 };
12242 });
12243 }
12244
12245
12246 function createShortMethodsWithData(name) {
12247 forEach(arguments, function(name) {
12248 $http[name] = function(url, data, config) {
12249 return $http(extend({}, config || {}, {
12250 method: name,
12251 url: url,
12252 data: data
12253 }));
12254 };
12255 });
12256 }
12257
12258
12259 /**
12260 * Makes the request.
12261 *
12262 * !!! ACCESSES CLOSURE VARS:
12263 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
12264 */
12265 function sendReq(config, reqData) {
12266 var deferred = $q.defer(),
12267 promise = deferred.promise,
12268 cache,
12269 cachedResp,
12270 reqHeaders = config.headers,
Ed Tanous4758d5b2017-06-06 15:28:13 -070012271 isJsonp = lowercase(config.method) === 'jsonp',
12272 url = config.url;
12273
12274 if (isJsonp) {
12275 // JSONP is a pretty sensitive operation where we're allowing a script to have full access to
12276 // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL.
12277 url = $sce.getTrustedResourceUrl(url);
12278 } else if (!isString(url)) {
12279 // If it is not a string then the URL must be a $sce trusted object
12280 url = $sce.valueOf(url);
12281 }
12282
12283 url = buildUrl(url, config.paramSerializer(config.params));
12284
12285 if (isJsonp) {
12286 // Check the url and add the JSONP callback placeholder
12287 url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
12288 }
Ed Tanous904063f2017-03-02 16:48:24 -080012289
12290 $http.pendingRequests.push(config);
12291 promise.then(removePendingReq, removePendingReq);
12292
Ed Tanous904063f2017-03-02 16:48:24 -080012293 if ((config.cache || defaults.cache) && config.cache !== false &&
12294 (config.method === 'GET' || config.method === 'JSONP')) {
12295 cache = isObject(config.cache) ? config.cache
Ed Tanous4758d5b2017-06-06 15:28:13 -070012296 : isObject(/** @type {?} */ (defaults).cache)
12297 ? /** @type {?} */ (defaults).cache
Ed Tanous904063f2017-03-02 16:48:24 -080012298 : defaultCache;
12299 }
12300
12301 if (cache) {
12302 cachedResp = cache.get(url);
12303 if (isDefined(cachedResp)) {
12304 if (isPromiseLike(cachedResp)) {
12305 // cached request has already been sent, but there is no response yet
12306 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
12307 } else {
12308 // serving from cache
12309 if (isArray(cachedResp)) {
12310 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
12311 } else {
12312 resolvePromise(cachedResp, 200, {}, 'OK');
12313 }
12314 }
12315 } else {
12316 // put the promise for the non-transformed response into cache as a placeholder
12317 cache.put(url, promise);
12318 }
12319 }
12320
12321
12322 // if we won't have the response in cache, set the xsrf headers and
12323 // send the request to the backend
12324 if (isUndefined(cachedResp)) {
12325 var xsrfValue = urlIsSameOrigin(config.url)
12326 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
12327 : undefined;
12328 if (xsrfValue) {
12329 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
12330 }
12331
12332 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
12333 config.withCredentials, config.responseType,
12334 createApplyHandlers(config.eventHandlers),
12335 createApplyHandlers(config.uploadEventHandlers));
12336 }
12337
12338 return promise;
12339
12340 function createApplyHandlers(eventHandlers) {
12341 if (eventHandlers) {
12342 var applyHandlers = {};
12343 forEach(eventHandlers, function(eventHandler, key) {
12344 applyHandlers[key] = function(event) {
12345 if (useApplyAsync) {
12346 $rootScope.$applyAsync(callEventHandler);
12347 } else if ($rootScope.$$phase) {
12348 callEventHandler();
12349 } else {
12350 $rootScope.$apply(callEventHandler);
12351 }
12352
12353 function callEventHandler() {
12354 eventHandler(event);
12355 }
12356 };
12357 });
12358 return applyHandlers;
12359 }
12360 }
12361
12362
12363 /**
12364 * Callback registered to $httpBackend():
12365 * - caches the response if desired
12366 * - resolves the raw $http promise
12367 * - calls $apply
12368 */
12369 function done(status, response, headersString, statusText) {
12370 if (cache) {
12371 if (isSuccess(status)) {
12372 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
12373 } else {
12374 // remove promise from the cache
12375 cache.remove(url);
12376 }
12377 }
12378
12379 function resolveHttpPromise() {
12380 resolvePromise(response, status, headersString, statusText);
12381 }
12382
12383 if (useApplyAsync) {
12384 $rootScope.$applyAsync(resolveHttpPromise);
12385 } else {
12386 resolveHttpPromise();
12387 if (!$rootScope.$$phase) $rootScope.$apply();
12388 }
12389 }
12390
12391
12392 /**
12393 * Resolves the raw $http promise.
12394 */
12395 function resolvePromise(response, status, headers, statusText) {
12396 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
12397 status = status >= -1 ? status : 0;
12398
12399 (isSuccess(status) ? deferred.resolve : deferred.reject)({
12400 data: response,
12401 status: status,
12402 headers: headersGetter(headers),
12403 config: config,
12404 statusText: statusText
12405 });
12406 }
12407
12408 function resolvePromiseWithResult(result) {
12409 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
12410 }
12411
12412 function removePendingReq() {
12413 var idx = $http.pendingRequests.indexOf(config);
12414 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
12415 }
12416 }
12417
12418
12419 function buildUrl(url, serializedParams) {
12420 if (serializedParams.length > 0) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012421 url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
Ed Tanous904063f2017-03-02 16:48:24 -080012422 }
12423 return url;
12424 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070012425
12426 function sanitizeJsonpCallbackParam(url, key) {
12427 if (/[&?][^=]+=JSON_CALLBACK/.test(url)) {
12428 // Throw if the url already contains a reference to JSON_CALLBACK
12429 throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url);
12430 }
12431
12432 var callbackParamRegex = new RegExp('[&?]' + key + '=');
12433 if (callbackParamRegex.test(url)) {
12434 // Throw if the callback param was already provided
12435 throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', key, url);
12436 }
12437
12438 // Add in the JSON_CALLBACK callback param value
12439 url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK';
12440
12441 return url;
12442 }
Ed Tanous904063f2017-03-02 16:48:24 -080012443 }];
12444}
12445
12446/**
12447 * @ngdoc service
12448 * @name $xhrFactory
Ed Tanous4758d5b2017-06-06 15:28:13 -070012449 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080012450 *
12451 * @description
12452 * Factory function used to create XMLHttpRequest objects.
12453 *
12454 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
12455 *
12456 * ```
12457 * angular.module('myApp', [])
12458 * .factory('$xhrFactory', function() {
12459 * return function createXhr(method, url) {
12460 * return new window.XMLHttpRequest({mozSystem: true});
12461 * };
12462 * });
12463 * ```
12464 *
12465 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
12466 * @param {string} url URL of the request.
12467 */
12468function $xhrFactoryProvider() {
12469 this.$get = function() {
12470 return function createXhr() {
12471 return new window.XMLHttpRequest();
12472 };
12473 };
12474}
12475
12476/**
12477 * @ngdoc service
12478 * @name $httpBackend
12479 * @requires $jsonpCallbacks
12480 * @requires $document
12481 * @requires $xhrFactory
Ed Tanous4758d5b2017-06-06 15:28:13 -070012482 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080012483 *
12484 * @description
12485 * HTTP backend used by the {@link ng.$http service} that delegates to
12486 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
12487 *
12488 * You should never need to use this service directly, instead use the higher-level abstractions:
12489 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
12490 *
12491 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
12492 * $httpBackend} which can be trained with responses.
12493 */
12494function $HttpBackendProvider() {
12495 this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) {
12496 return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]);
12497 }];
12498}
12499
12500function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
12501 // TODO(vojta): fix the signature
12502 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
Ed Tanous904063f2017-03-02 16:48:24 -080012503 url = url || $browser.url();
12504
12505 if (lowercase(method) === 'jsonp') {
12506 var callbackPath = callbacks.createCallback(url);
12507 var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
12508 // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
12509 var response = (status === 200) && callbacks.getResponse(callbackPath);
Ed Tanous4758d5b2017-06-06 15:28:13 -070012510 completeRequest(callback, status, response, '', text);
Ed Tanous904063f2017-03-02 16:48:24 -080012511 callbacks.removeCallback(callbackPath);
12512 });
12513 } else {
12514
12515 var xhr = createXhr(method, url);
12516
12517 xhr.open(method, url, true);
12518 forEach(headers, function(value, key) {
12519 if (isDefined(value)) {
12520 xhr.setRequestHeader(key, value);
12521 }
12522 });
12523
12524 xhr.onload = function requestLoaded() {
12525 var statusText = xhr.statusText || '';
12526
12527 // responseText is the old-school way of retrieving response (supported by IE9)
12528 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
12529 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
12530
12531 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
12532 var status = xhr.status === 1223 ? 204 : xhr.status;
12533
12534 // fix status code when it is 0 (0 status is undocumented).
12535 // Occurs when accessing file resources or on Android 4.1 stock browser
12536 // while retrieving files from application cache.
12537 if (status === 0) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012538 status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0;
Ed Tanous904063f2017-03-02 16:48:24 -080012539 }
12540
12541 completeRequest(callback,
12542 status,
12543 response,
12544 xhr.getAllResponseHeaders(),
12545 statusText);
12546 };
12547
12548 var requestError = function() {
12549 // The response is always empty
12550 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
12551 completeRequest(callback, -1, null, null, '');
12552 };
12553
12554 xhr.onerror = requestError;
12555 xhr.onabort = requestError;
Ed Tanous4758d5b2017-06-06 15:28:13 -070012556 xhr.ontimeout = requestError;
Ed Tanous904063f2017-03-02 16:48:24 -080012557
12558 forEach(eventHandlers, function(value, key) {
12559 xhr.addEventListener(key, value);
12560 });
12561
12562 forEach(uploadEventHandlers, function(value, key) {
12563 xhr.upload.addEventListener(key, value);
12564 });
12565
12566 if (withCredentials) {
12567 xhr.withCredentials = true;
12568 }
12569
12570 if (responseType) {
12571 try {
12572 xhr.responseType = responseType;
12573 } catch (e) {
12574 // WebKit added support for the json responseType value on 09/03/2013
12575 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
12576 // known to throw when setting the value "json" as the response type. Other older
12577 // browsers implementing the responseType
12578 //
12579 // The json response type can be ignored if not supported, because JSON payloads are
12580 // parsed on the client-side regardless.
12581 if (responseType !== 'json') {
12582 throw e;
12583 }
12584 }
12585 }
12586
12587 xhr.send(isUndefined(post) ? null : post);
12588 }
12589
12590 if (timeout > 0) {
12591 var timeoutId = $browserDefer(timeoutRequest, timeout);
12592 } else if (isPromiseLike(timeout)) {
12593 timeout.then(timeoutRequest);
12594 }
12595
12596
12597 function timeoutRequest() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012598 if (jsonpDone) {
12599 jsonpDone();
12600 }
12601 if (xhr) {
12602 xhr.abort();
12603 }
Ed Tanous904063f2017-03-02 16:48:24 -080012604 }
12605
12606 function completeRequest(callback, status, response, headersString, statusText) {
12607 // cancel timeout and subsequent timeout promise resolution
12608 if (isDefined(timeoutId)) {
12609 $browserDefer.cancel(timeoutId);
12610 }
12611 jsonpDone = xhr = null;
12612
12613 callback(status, response, headersString, statusText);
Ed Tanous904063f2017-03-02 16:48:24 -080012614 }
12615 };
12616
12617 function jsonpReq(url, callbackPath, done) {
12618 url = url.replace('JSON_CALLBACK', callbackPath);
12619 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
12620 // - fetches local scripts via XHR and evals them
12621 // - adds and immediately removes script elements from the document
12622 var script = rawDocument.createElement('script'), callback = null;
Ed Tanous4758d5b2017-06-06 15:28:13 -070012623 script.type = 'text/javascript';
Ed Tanous904063f2017-03-02 16:48:24 -080012624 script.src = url;
12625 script.async = true;
12626
12627 callback = function(event) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012628 script.removeEventListener('load', callback);
12629 script.removeEventListener('error', callback);
Ed Tanous904063f2017-03-02 16:48:24 -080012630 rawDocument.body.removeChild(script);
12631 script = null;
12632 var status = -1;
Ed Tanous4758d5b2017-06-06 15:28:13 -070012633 var text = 'unknown';
Ed Tanous904063f2017-03-02 16:48:24 -080012634
12635 if (event) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012636 if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) {
12637 event = { type: 'error' };
Ed Tanous904063f2017-03-02 16:48:24 -080012638 }
12639 text = event.type;
Ed Tanous4758d5b2017-06-06 15:28:13 -070012640 status = event.type === 'error' ? 404 : 200;
Ed Tanous904063f2017-03-02 16:48:24 -080012641 }
12642
12643 if (done) {
12644 done(status, text);
12645 }
12646 };
12647
Ed Tanous4758d5b2017-06-06 15:28:13 -070012648 script.addEventListener('load', callback);
12649 script.addEventListener('error', callback);
Ed Tanous904063f2017-03-02 16:48:24 -080012650 rawDocument.body.appendChild(script);
12651 return callback;
12652 }
12653}
12654
12655var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
12656$interpolateMinErr.throwNoconcat = function(text) {
12657 throw $interpolateMinErr('noconcat',
Ed Tanous4758d5b2017-06-06 15:28:13 -070012658 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' +
12659 'interpolations that concatenate multiple expressions when a trusted value is ' +
12660 'required. See http://docs.angularjs.org/api/ng.$sce', text);
Ed Tanous904063f2017-03-02 16:48:24 -080012661};
12662
12663$interpolateMinErr.interr = function(text, err) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012664 return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString());
Ed Tanous904063f2017-03-02 16:48:24 -080012665};
12666
12667/**
12668 * @ngdoc provider
12669 * @name $interpolateProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070012670 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080012671 *
12672 * @description
12673 *
12674 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
12675 *
12676 * <div class="alert alert-danger">
12677 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
12678 * template within a Python Jinja template (or any other template language). Mixing templating
12679 * languages is **very dangerous**. The embedding template language will not safely escape Angular
12680 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
12681 * security bugs!
12682 * </div>
12683 *
12684 * @example
12685<example name="custom-interpolation-markup" module="customInterpolationApp">
12686<file name="index.html">
12687<script>
12688 var customInterpolationApp = angular.module('customInterpolationApp', []);
12689
12690 customInterpolationApp.config(function($interpolateProvider) {
12691 $interpolateProvider.startSymbol('//');
12692 $interpolateProvider.endSymbol('//');
12693 });
12694
12695
12696 customInterpolationApp.controller('DemoController', function() {
12697 this.label = "This binding is brought you by // interpolation symbols.";
12698 });
12699</script>
12700<div ng-controller="DemoController as demo">
12701 //demo.label//
12702</div>
12703</file>
12704<file name="protractor.js" type="protractor">
12705 it('should interpolate binding with custom symbols', function() {
12706 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
12707 });
12708</file>
12709</example>
12710 */
12711function $InterpolateProvider() {
12712 var startSymbol = '{{';
12713 var endSymbol = '}}';
12714
12715 /**
12716 * @ngdoc method
12717 * @name $interpolateProvider#startSymbol
12718 * @description
12719 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
12720 *
12721 * @param {string=} value new value to set the starting symbol to.
12722 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
12723 */
12724 this.startSymbol = function(value) {
12725 if (value) {
12726 startSymbol = value;
12727 return this;
12728 } else {
12729 return startSymbol;
12730 }
12731 };
12732
12733 /**
12734 * @ngdoc method
12735 * @name $interpolateProvider#endSymbol
12736 * @description
12737 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
12738 *
12739 * @param {string=} value new value to set the ending symbol to.
12740 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
12741 */
12742 this.endSymbol = function(value) {
12743 if (value) {
12744 endSymbol = value;
12745 return this;
12746 } else {
12747 return endSymbol;
12748 }
12749 };
12750
12751
12752 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
12753 var startSymbolLength = startSymbol.length,
12754 endSymbolLength = endSymbol.length,
12755 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
12756 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
12757
12758 function escape(ch) {
12759 return '\\\\\\' + ch;
12760 }
12761
12762 function unescapeText(text) {
12763 return text.replace(escapedStartRegexp, startSymbol).
12764 replace(escapedEndRegexp, endSymbol);
12765 }
12766
Ed Tanous4758d5b2017-06-06 15:28:13 -070012767 // TODO: this is the same as the constantWatchDelegate in parse.js
Ed Tanous904063f2017-03-02 16:48:24 -080012768 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012769 var unwatch = scope.$watch(function constantInterpolateWatch(scope) {
Ed Tanous904063f2017-03-02 16:48:24 -080012770 unwatch();
12771 return constantInterp(scope);
12772 }, listener, objectEquality);
Ed Tanous4758d5b2017-06-06 15:28:13 -070012773 return unwatch;
Ed Tanous904063f2017-03-02 16:48:24 -080012774 }
12775
12776 /**
12777 * @ngdoc service
12778 * @name $interpolate
12779 * @kind function
12780 *
12781 * @requires $parse
12782 * @requires $sce
12783 *
12784 * @description
12785 *
12786 * Compiles a string with markup into an interpolation function. This service is used by the
12787 * HTML {@link ng.$compile $compile} service for data binding. See
12788 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
12789 * interpolation markup.
12790 *
12791 *
12792 * ```js
12793 * var $interpolate = ...; // injected
12794 * var exp = $interpolate('Hello {{name | uppercase}}!');
12795 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
12796 * ```
12797 *
12798 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
12799 * `true`, the interpolation function will return `undefined` unless all embedded expressions
12800 * evaluate to a value other than `undefined`.
12801 *
12802 * ```js
12803 * var $interpolate = ...; // injected
12804 * var context = {greeting: 'Hello', name: undefined };
12805 *
12806 * // default "forgiving" mode
12807 * var exp = $interpolate('{{greeting}} {{name}}!');
12808 * expect(exp(context)).toEqual('Hello !');
12809 *
12810 * // "allOrNothing" mode
12811 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
12812 * expect(exp(context)).toBeUndefined();
12813 * context.name = 'Angular';
12814 * expect(exp(context)).toEqual('Hello Angular!');
12815 * ```
12816 *
12817 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
12818 *
12819 * #### Escaped Interpolation
12820 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
12821 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
12822 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
12823 * or binding.
12824 *
12825 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
12826 * degree, while also enabling code examples to work without relying on the
12827 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
12828 *
12829 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
12830 * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
12831 * interpolation start/end markers with their escaped counterparts.**
12832 *
12833 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
12834 * output when the $interpolate service processes the text. So, for HTML elements interpolated
12835 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
12836 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
12837 * this is typically useful only when user-data is used in rendering a template from the server, or
12838 * when otherwise untrusted data is used by a directive.
12839 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070012840 * <example name="interpolation">
Ed Tanous904063f2017-03-02 16:48:24 -080012841 * <file name="index.html">
12842 * <div ng-init="username='A user'">
12843 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
12844 * </p>
12845 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
12846 * application, but fails to accomplish their task, because the server has correctly
12847 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
12848 * characters.</p>
12849 * <p>Instead, the result of the attempted script injection is visible, and can be removed
12850 * from the database by an administrator.</p>
12851 * </div>
12852 * </file>
12853 * </example>
12854 *
12855 * @knownIssue
12856 * It is currently not possible for an interpolated expression to contain the interpolation end
12857 * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
12858 * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
12859 *
12860 * @knownIssue
12861 * All directives and components must use the standard `{{` `}}` interpolation symbols
12862 * in their templates. If you change the application interpolation symbols the {@link $compile}
12863 * service will attempt to denormalize the standard symbols to the custom symbols.
12864 * The denormalization process is not clever enough to know not to replace instances of the standard
12865 * symbols where they would not normally be treated as interpolation symbols. For example in the following
12866 * code snippet the closing braces of the literal object will get incorrectly denormalized:
12867 *
12868 * ```
12869 * <div data-context='{"context":{"id":3,"type":"page"}}">
12870 * ```
12871 *
12872 * The workaround is to ensure that such instances are separated by whitespace:
12873 * ```
12874 * <div data-context='{"context":{"id":3,"type":"page"} }">
12875 * ```
12876 *
12877 * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information.
12878 *
12879 * @param {string} text The text with markup to interpolate.
12880 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
12881 * embedded expression in order to return an interpolation function. Strings with no
12882 * embedded expression will return null for the interpolation function.
12883 * @param {string=} trustedContext when provided, the returned function passes the interpolated
12884 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
12885 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
12886 * provides Strict Contextual Escaping for details.
12887 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
12888 * unless all embedded expressions evaluate to a value other than `undefined`.
12889 * @returns {function(context)} an interpolation function which is used to compute the
12890 * interpolated string. The function has these parameters:
12891 *
12892 * - `context`: evaluation context for all expressions embedded in the interpolated text
12893 */
12894 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
12895 // Provide a quick exit and simplified result function for text with no interpolation
12896 if (!text.length || text.indexOf(startSymbol) === -1) {
12897 var constantInterp;
12898 if (!mustHaveExpression) {
12899 var unescapedText = unescapeText(text);
12900 constantInterp = valueFn(unescapedText);
12901 constantInterp.exp = text;
12902 constantInterp.expressions = [];
12903 constantInterp.$$watchDelegate = constantWatchDelegate;
12904 }
12905 return constantInterp;
12906 }
12907
12908 allOrNothing = !!allOrNothing;
12909 var startIndex,
12910 endIndex,
12911 index = 0,
12912 expressions = [],
12913 parseFns = [],
12914 textLength = text.length,
12915 exp,
12916 concat = [],
12917 expressionPositions = [];
12918
12919 while (index < textLength) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070012920 if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
12921 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
Ed Tanous904063f2017-03-02 16:48:24 -080012922 if (index !== startIndex) {
12923 concat.push(unescapeText(text.substring(index, startIndex)));
12924 }
12925 exp = text.substring(startIndex + startSymbolLength, endIndex);
12926 expressions.push(exp);
12927 parseFns.push($parse(exp, parseStringifyInterceptor));
12928 index = endIndex + endSymbolLength;
12929 expressionPositions.push(concat.length);
12930 concat.push('');
12931 } else {
12932 // we did not find an interpolation, so we have to add the remainder to the separators array
12933 if (index !== textLength) {
12934 concat.push(unescapeText(text.substring(index)));
12935 }
12936 break;
12937 }
12938 }
12939
12940 // Concatenating expressions makes it hard to reason about whether some combination of
12941 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
12942 // single expression be used for iframe[src], object[src], etc., we ensure that the value
12943 // that's used is assigned or constructed by some JS code somewhere that is more testable or
12944 // make it obvious that you bound the value to some user controlled value. This helps reduce
12945 // the load when auditing for XSS issues.
12946 if (trustedContext && concat.length > 1) {
12947 $interpolateMinErr.throwNoconcat(text);
12948 }
12949
12950 if (!mustHaveExpression || expressions.length) {
12951 var compute = function(values) {
12952 for (var i = 0, ii = expressions.length; i < ii; i++) {
12953 if (allOrNothing && isUndefined(values[i])) return;
12954 concat[expressionPositions[i]] = values[i];
12955 }
12956 return concat.join('');
12957 };
12958
12959 var getValue = function(value) {
12960 return trustedContext ?
12961 $sce.getTrusted(trustedContext, value) :
12962 $sce.valueOf(value);
12963 };
12964
12965 return extend(function interpolationFn(context) {
12966 var i = 0;
12967 var ii = expressions.length;
12968 var values = new Array(ii);
12969
12970 try {
12971 for (; i < ii; i++) {
12972 values[i] = parseFns[i](context);
12973 }
12974
12975 return compute(values);
12976 } catch (err) {
12977 $exceptionHandler($interpolateMinErr.interr(text, err));
12978 }
12979
12980 }, {
12981 // all of these properties are undocumented for now
12982 exp: text, //just for compatibility with regular watchers created via $watch
12983 expressions: expressions,
12984 $$watchDelegate: function(scope, listener) {
12985 var lastValue;
Ed Tanous4758d5b2017-06-06 15:28:13 -070012986 return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) {
Ed Tanous904063f2017-03-02 16:48:24 -080012987 var currValue = compute(values);
12988 if (isFunction(listener)) {
12989 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
12990 }
12991 lastValue = currValue;
12992 });
12993 }
12994 });
12995 }
12996
12997 function parseStringifyInterceptor(value) {
12998 try {
12999 value = getValue(value);
13000 return allOrNothing && !isDefined(value) ? value : stringify(value);
13001 } catch (err) {
13002 $exceptionHandler($interpolateMinErr.interr(text, err));
13003 }
13004 }
13005 }
13006
13007
13008 /**
13009 * @ngdoc method
13010 * @name $interpolate#startSymbol
13011 * @description
13012 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
13013 *
13014 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
13015 * the symbol.
13016 *
13017 * @returns {string} start symbol.
13018 */
13019 $interpolate.startSymbol = function() {
13020 return startSymbol;
13021 };
13022
13023
13024 /**
13025 * @ngdoc method
13026 * @name $interpolate#endSymbol
13027 * @description
13028 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
13029 *
13030 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
13031 * the symbol.
13032 *
13033 * @returns {string} end symbol.
13034 */
13035 $interpolate.endSymbol = function() {
13036 return endSymbol;
13037 };
13038
13039 return $interpolate;
13040 }];
13041}
13042
Ed Tanous4758d5b2017-06-06 15:28:13 -070013043/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080013044function $IntervalProvider() {
13045 this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
13046 function($rootScope, $window, $q, $$q, $browser) {
13047 var intervals = {};
13048
13049
13050 /**
13051 * @ngdoc service
13052 * @name $interval
13053 *
13054 * @description
13055 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
13056 * milliseconds.
13057 *
13058 * The return value of registering an interval function is a promise. This promise will be
13059 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
13060 * run indefinitely if `count` is not defined. The value of the notification will be the
13061 * number of iterations that have run.
13062 * To cancel an interval, call `$interval.cancel(promise)`.
13063 *
13064 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
13065 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
13066 * time.
13067 *
13068 * <div class="alert alert-warning">
13069 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
13070 * with them. In particular they are not automatically destroyed when a controller's scope or a
13071 * directive's element are destroyed.
13072 * You should take this into consideration and make sure to always cancel the interval at the
13073 * appropriate moment. See the example below for more details on how and when to do this.
13074 * </div>
13075 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013076 * @param {function()} fn A function that should be called repeatedly. If no additional arguments
13077 * are passed (see below), the function is called with the current iteration count.
Ed Tanous904063f2017-03-02 16:48:24 -080013078 * @param {number} delay Number of milliseconds between each function call.
13079 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
13080 * indefinitely.
13081 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
13082 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
13083 * @param {...*=} Pass additional parameters to the executed function.
Ed Tanous4758d5b2017-06-06 15:28:13 -070013084 * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete.
Ed Tanous904063f2017-03-02 16:48:24 -080013085 *
13086 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070013087 * <example module="intervalExample" name="interval-service">
Ed Tanous904063f2017-03-02 16:48:24 -080013088 * <file name="index.html">
13089 * <script>
13090 * angular.module('intervalExample', [])
13091 * .controller('ExampleController', ['$scope', '$interval',
13092 * function($scope, $interval) {
13093 * $scope.format = 'M/d/yy h:mm:ss a';
13094 * $scope.blood_1 = 100;
13095 * $scope.blood_2 = 120;
13096 *
13097 * var stop;
13098 * $scope.fight = function() {
13099 * // Don't start a new fight if we are already fighting
13100 * if ( angular.isDefined(stop) ) return;
13101 *
13102 * stop = $interval(function() {
13103 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
13104 * $scope.blood_1 = $scope.blood_1 - 3;
13105 * $scope.blood_2 = $scope.blood_2 - 4;
13106 * } else {
13107 * $scope.stopFight();
13108 * }
13109 * }, 100);
13110 * };
13111 *
13112 * $scope.stopFight = function() {
13113 * if (angular.isDefined(stop)) {
13114 * $interval.cancel(stop);
13115 * stop = undefined;
13116 * }
13117 * };
13118 *
13119 * $scope.resetFight = function() {
13120 * $scope.blood_1 = 100;
13121 * $scope.blood_2 = 120;
13122 * };
13123 *
13124 * $scope.$on('$destroy', function() {
13125 * // Make sure that the interval is destroyed too
13126 * $scope.stopFight();
13127 * });
13128 * }])
13129 * // Register the 'myCurrentTime' directive factory method.
13130 * // We inject $interval and dateFilter service since the factory method is DI.
13131 * .directive('myCurrentTime', ['$interval', 'dateFilter',
13132 * function($interval, dateFilter) {
13133 * // return the directive link function. (compile function not needed)
13134 * return function(scope, element, attrs) {
13135 * var format, // date format
13136 * stopTime; // so that we can cancel the time updates
13137 *
13138 * // used to update the UI
13139 * function updateTime() {
13140 * element.text(dateFilter(new Date(), format));
13141 * }
13142 *
13143 * // watch the expression, and update the UI on change.
13144 * scope.$watch(attrs.myCurrentTime, function(value) {
13145 * format = value;
13146 * updateTime();
13147 * });
13148 *
13149 * stopTime = $interval(updateTime, 1000);
13150 *
13151 * // listen on DOM destroy (removal) event, and cancel the next UI update
13152 * // to prevent updating time after the DOM element was removed.
13153 * element.on('$destroy', function() {
13154 * $interval.cancel(stopTime);
13155 * });
13156 * }
13157 * }]);
13158 * </script>
13159 *
13160 * <div>
13161 * <div ng-controller="ExampleController">
13162 * <label>Date format: <input ng-model="format"></label> <hr/>
13163 * Current time is: <span my-current-time="format"></span>
13164 * <hr/>
13165 * Blood 1 : <font color='red'>{{blood_1}}</font>
13166 * Blood 2 : <font color='red'>{{blood_2}}</font>
13167 * <button type="button" data-ng-click="fight()">Fight</button>
13168 * <button type="button" data-ng-click="stopFight()">StopFight</button>
13169 * <button type="button" data-ng-click="resetFight()">resetFight</button>
13170 * </div>
13171 * </div>
13172 *
13173 * </file>
13174 * </example>
13175 */
13176 function interval(fn, delay, count, invokeApply) {
13177 var hasParams = arguments.length > 4,
13178 args = hasParams ? sliceArgs(arguments, 4) : [],
13179 setInterval = $window.setInterval,
13180 clearInterval = $window.clearInterval,
13181 iteration = 0,
13182 skipApply = (isDefined(invokeApply) && !invokeApply),
13183 deferred = (skipApply ? $$q : $q).defer(),
13184 promise = deferred.promise;
13185
13186 count = isDefined(count) ? count : 0;
13187
13188 promise.$$intervalId = setInterval(function tick() {
13189 if (skipApply) {
13190 $browser.defer(callback);
13191 } else {
13192 $rootScope.$evalAsync(callback);
13193 }
13194 deferred.notify(iteration++);
13195
13196 if (count > 0 && iteration >= count) {
13197 deferred.resolve(iteration);
13198 clearInterval(promise.$$intervalId);
13199 delete intervals[promise.$$intervalId];
13200 }
13201
13202 if (!skipApply) $rootScope.$apply();
13203
13204 }, delay);
13205
13206 intervals[promise.$$intervalId] = deferred;
13207
13208 return promise;
13209
13210 function callback() {
13211 if (!hasParams) {
13212 fn(iteration);
13213 } else {
13214 fn.apply(null, args);
13215 }
13216 }
13217 }
13218
13219
13220 /**
13221 * @ngdoc method
13222 * @name $interval#cancel
13223 *
13224 * @description
13225 * Cancels a task associated with the `promise`.
13226 *
13227 * @param {Promise=} promise returned by the `$interval` function.
13228 * @returns {boolean} Returns `true` if the task was successfully canceled.
13229 */
13230 interval.cancel = function(promise) {
13231 if (promise && promise.$$intervalId in intervals) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070013232 // Interval cancels should not report as unhandled promise.
13233 intervals[promise.$$intervalId].promise.catch(noop);
Ed Tanous904063f2017-03-02 16:48:24 -080013234 intervals[promise.$$intervalId].reject('canceled');
13235 $window.clearInterval(promise.$$intervalId);
13236 delete intervals[promise.$$intervalId];
13237 return true;
13238 }
13239 return false;
13240 };
13241
13242 return interval;
13243 }];
13244}
13245
13246/**
13247 * @ngdoc service
13248 * @name $jsonpCallbacks
13249 * @requires $window
13250 * @description
13251 * This service handles the lifecycle of callbacks to handle JSONP requests.
13252 * Override this service if you wish to customise where the callbacks are stored and
13253 * how they vary compared to the requested url.
13254 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070013255var $jsonpCallbacksProvider = /** @this */ function() {
13256 this.$get = function() {
13257 var callbacks = angular.callbacks;
Ed Tanous904063f2017-03-02 16:48:24 -080013258 var callbackMap = {};
13259
13260 function createCallback(callbackId) {
13261 var callback = function(data) {
13262 callback.data = data;
13263 callback.called = true;
13264 };
13265 callback.id = callbackId;
13266 return callback;
13267 }
13268
13269 return {
13270 /**
13271 * @ngdoc method
13272 * @name $jsonpCallbacks#createCallback
13273 * @param {string} url the url of the JSONP request
13274 * @returns {string} the callback path to send to the server as part of the JSONP request
13275 * @description
13276 * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
13277 * to pass to the server, which will be used to call the callback with its payload in the JSONP response.
13278 */
13279 createCallback: function(url) {
13280 var callbackId = '_' + (callbacks.$$counter++).toString(36);
13281 var callbackPath = 'angular.callbacks.' + callbackId;
13282 var callback = createCallback(callbackId);
13283 callbackMap[callbackPath] = callbacks[callbackId] = callback;
13284 return callbackPath;
13285 },
13286 /**
13287 * @ngdoc method
13288 * @name $jsonpCallbacks#wasCalled
13289 * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13290 * @returns {boolean} whether the callback has been called, as a result of the JSONP response
13291 * @description
13292 * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
13293 * callback that was passed in the request.
13294 */
13295 wasCalled: function(callbackPath) {
13296 return callbackMap[callbackPath].called;
13297 },
13298 /**
13299 * @ngdoc method
13300 * @name $jsonpCallbacks#getResponse
13301 * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13302 * @returns {*} the data received from the response via the registered callback
13303 * @description
13304 * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
13305 * in the JSONP response.
13306 */
13307 getResponse: function(callbackPath) {
13308 return callbackMap[callbackPath].data;
13309 },
13310 /**
13311 * @ngdoc method
13312 * @name $jsonpCallbacks#removeCallback
13313 * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13314 * @description
13315 * {@link $httpBackend} calls this method to remove the callback after the JSONP request has
13316 * completed or timed-out.
13317 */
13318 removeCallback: function(callbackPath) {
13319 var callback = callbackMap[callbackPath];
13320 delete callbacks[callback.id];
13321 delete callbackMap[callbackPath];
13322 }
13323 };
Ed Tanous4758d5b2017-06-06 15:28:13 -070013324 };
Ed Tanous904063f2017-03-02 16:48:24 -080013325};
13326
13327/**
13328 * @ngdoc service
13329 * @name $locale
13330 *
13331 * @description
13332 * $locale service provides localization rules for various Angular components. As of right now the
13333 * only public api is:
13334 *
13335 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
13336 */
13337
Ed Tanous4758d5b2017-06-06 15:28:13 -070013338var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
Ed Tanous904063f2017-03-02 16:48:24 -080013339 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
13340var $locationMinErr = minErr('$location');
13341
13342
13343/**
13344 * Encode path using encodeUriSegment, ignoring forward slashes
13345 *
13346 * @param {string} path Path to encode
13347 * @returns {string}
13348 */
13349function encodePath(path) {
13350 var segments = path.split('/'),
13351 i = segments.length;
13352
13353 while (i--) {
13354 segments[i] = encodeUriSegment(segments[i]);
13355 }
13356
13357 return segments.join('/');
13358}
13359
13360function parseAbsoluteUrl(absoluteUrl, locationObj) {
13361 var parsedUrl = urlResolve(absoluteUrl);
13362
13363 locationObj.$$protocol = parsedUrl.protocol;
13364 locationObj.$$host = parsedUrl.hostname;
13365 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
13366}
13367
Ed Tanous4758d5b2017-06-06 15:28:13 -070013368var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
13369function parseAppUrl(url, locationObj) {
Ed Tanous904063f2017-03-02 16:48:24 -080013370
Ed Tanous4758d5b2017-06-06 15:28:13 -070013371 if (DOUBLE_SLASH_REGEX.test(url)) {
13372 throw $locationMinErr('badpath', 'Invalid url "{0}".', url);
Ed Tanous904063f2017-03-02 16:48:24 -080013373 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070013374
13375 var prefixed = (url.charAt(0) !== '/');
13376 if (prefixed) {
13377 url = '/' + url;
13378 }
13379 var match = urlResolve(url);
Ed Tanous904063f2017-03-02 16:48:24 -080013380 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
13381 match.pathname.substring(1) : match.pathname);
13382 locationObj.$$search = parseKeyValue(match.search);
13383 locationObj.$$hash = decodeURIComponent(match.hash);
13384
13385 // make sure path starts with '/';
Ed Tanous4758d5b2017-06-06 15:28:13 -070013386 if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') {
Ed Tanous904063f2017-03-02 16:48:24 -080013387 locationObj.$$path = '/' + locationObj.$$path;
13388 }
13389}
13390
Ed Tanous4758d5b2017-06-06 15:28:13 -070013391function startsWith(str, search) {
13392 return str.slice(0, search.length) === search;
Ed Tanous904063f2017-03-02 16:48:24 -080013393}
13394
13395/**
13396 *
13397 * @param {string} base
13398 * @param {string} url
13399 * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
13400 * the expected string.
13401 */
13402function stripBaseUrl(base, url) {
13403 if (startsWith(url, base)) {
13404 return url.substr(base.length);
13405 }
13406}
13407
13408
13409function stripHash(url) {
13410 var index = url.indexOf('#');
Ed Tanous4758d5b2017-06-06 15:28:13 -070013411 return index === -1 ? url : url.substr(0, index);
Ed Tanous904063f2017-03-02 16:48:24 -080013412}
13413
13414function trimEmptyHash(url) {
13415 return url.replace(/(#.+)|#$/, '$1');
13416}
13417
13418
13419function stripFile(url) {
13420 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
13421}
13422
13423/* return the server only (scheme://host:port) */
13424function serverBase(url) {
13425 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
13426}
13427
13428
13429/**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013430 * LocationHtml5Url represents a URL
Ed Tanous904063f2017-03-02 16:48:24 -080013431 * This object is exposed as $location service when HTML5 mode is enabled and supported
13432 *
13433 * @constructor
13434 * @param {string} appBase application base URL
13435 * @param {string} appBaseNoFile application base URL stripped of any filename
Ed Tanous4758d5b2017-06-06 15:28:13 -070013436 * @param {string} basePrefix URL path prefix
Ed Tanous904063f2017-03-02 16:48:24 -080013437 */
13438function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
13439 this.$$html5 = true;
13440 basePrefix = basePrefix || '';
13441 parseAbsoluteUrl(appBase, this);
13442
13443
13444 /**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013445 * Parse given HTML5 (regular) URL string into properties
13446 * @param {string} url HTML5 URL
Ed Tanous904063f2017-03-02 16:48:24 -080013447 * @private
13448 */
13449 this.$$parse = function(url) {
13450 var pathUrl = stripBaseUrl(appBaseNoFile, url);
13451 if (!isString(pathUrl)) {
13452 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
13453 appBaseNoFile);
13454 }
13455
13456 parseAppUrl(pathUrl, this);
13457
13458 if (!this.$$path) {
13459 this.$$path = '/';
13460 }
13461
13462 this.$$compose();
13463 };
13464
13465 /**
13466 * Compose url and update `absUrl` property
13467 * @private
13468 */
13469 this.$$compose = function() {
13470 var search = toKeyValue(this.$$search),
13471 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13472
13473 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13474 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
Ed Tanous4758d5b2017-06-06 15:28:13 -070013475
13476 this.$$urlUpdatedByLocation = true;
Ed Tanous904063f2017-03-02 16:48:24 -080013477 };
13478
13479 this.$$parseLinkUrl = function(url, relHref) {
13480 if (relHref && relHref[0] === '#') {
13481 // special case for links to hash fragments:
13482 // keep the old url and only replace the hash fragment
13483 this.hash(relHref.slice(1));
13484 return true;
13485 }
13486 var appUrl, prevAppUrl;
13487 var rewrittenUrl;
13488
Ed Tanous4758d5b2017-06-06 15:28:13 -070013489
Ed Tanous904063f2017-03-02 16:48:24 -080013490 if (isDefined(appUrl = stripBaseUrl(appBase, url))) {
13491 prevAppUrl = appUrl;
Ed Tanous4758d5b2017-06-06 15:28:13 -070013492 if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) {
Ed Tanous904063f2017-03-02 16:48:24 -080013493 rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl);
13494 } else {
13495 rewrittenUrl = appBase + prevAppUrl;
13496 }
13497 } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) {
13498 rewrittenUrl = appBaseNoFile + appUrl;
Ed Tanous4758d5b2017-06-06 15:28:13 -070013499 } else if (appBaseNoFile === url + '/') {
Ed Tanous904063f2017-03-02 16:48:24 -080013500 rewrittenUrl = appBaseNoFile;
13501 }
13502 if (rewrittenUrl) {
13503 this.$$parse(rewrittenUrl);
13504 }
13505 return !!rewrittenUrl;
13506 };
13507}
13508
13509
13510/**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013511 * LocationHashbangUrl represents URL
Ed Tanous904063f2017-03-02 16:48:24 -080013512 * This object is exposed as $location service when developer doesn't opt into html5 mode.
13513 * It also serves as the base class for html5 mode fallback on legacy browsers.
13514 *
13515 * @constructor
13516 * @param {string} appBase application base URL
13517 * @param {string} appBaseNoFile application base URL stripped of any filename
13518 * @param {string} hashPrefix hashbang prefix
13519 */
13520function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
13521
13522 parseAbsoluteUrl(appBase, this);
13523
13524
13525 /**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013526 * Parse given hashbang URL into properties
13527 * @param {string} url Hashbang URL
Ed Tanous904063f2017-03-02 16:48:24 -080013528 * @private
13529 */
13530 this.$$parse = function(url) {
13531 var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url);
13532 var withoutHashUrl;
13533
13534 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
13535
Ed Tanous4758d5b2017-06-06 15:28:13 -070013536 // The rest of the URL starts with a hash so we have
Ed Tanous904063f2017-03-02 16:48:24 -080013537 // got either a hashbang path or a plain hash fragment
13538 withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
13539 if (isUndefined(withoutHashUrl)) {
13540 // There was no hashbang prefix so we just have a hash fragment
13541 withoutHashUrl = withoutBaseUrl;
13542 }
13543
13544 } else {
13545 // There was no hashbang path nor hash fragment:
13546 // If we are in HTML5 mode we use what is left as the path;
13547 // Otherwise we ignore what is left
13548 if (this.$$html5) {
13549 withoutHashUrl = withoutBaseUrl;
13550 } else {
13551 withoutHashUrl = '';
13552 if (isUndefined(withoutBaseUrl)) {
13553 appBase = url;
Ed Tanous4758d5b2017-06-06 15:28:13 -070013554 /** @type {?} */ (this).replace();
Ed Tanous904063f2017-03-02 16:48:24 -080013555 }
13556 }
13557 }
13558
13559 parseAppUrl(withoutHashUrl, this);
13560
13561 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
13562
13563 this.$$compose();
13564
13565 /*
13566 * In Windows, on an anchor node on documents loaded from
13567 * the filesystem, the browser will return a pathname
13568 * prefixed with the drive name ('/C:/path') when a
13569 * pathname without a drive is set:
13570 * * a.setAttribute('href', '/foo')
13571 * * a.pathname === '/C:/foo' //true
13572 *
13573 * Inside of Angular, we're always using pathnames that
13574 * do not include drive names for routing.
13575 */
13576 function removeWindowsDriveName(path, url, base) {
13577 /*
13578 Matches paths for file protocol on windows,
13579 such as /C:/foo/bar, and captures only /foo/bar.
13580 */
13581 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
13582
13583 var firstPathSegmentMatch;
13584
13585 //Get the relative path from the input URL.
13586 if (startsWith(url, base)) {
13587 url = url.replace(base, '');
13588 }
13589
13590 // The input URL intentionally contains a first path segment that ends with a colon.
13591 if (windowsFilePathExp.exec(url)) {
13592 return path;
13593 }
13594
13595 firstPathSegmentMatch = windowsFilePathExp.exec(path);
13596 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
13597 }
13598 };
13599
13600 /**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013601 * Compose hashbang URL and update `absUrl` property
Ed Tanous904063f2017-03-02 16:48:24 -080013602 * @private
13603 */
13604 this.$$compose = function() {
13605 var search = toKeyValue(this.$$search),
13606 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13607
13608 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13609 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
Ed Tanous4758d5b2017-06-06 15:28:13 -070013610
13611 this.$$urlUpdatedByLocation = true;
Ed Tanous904063f2017-03-02 16:48:24 -080013612 };
13613
13614 this.$$parseLinkUrl = function(url, relHref) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070013615 if (stripHash(appBase) === stripHash(url)) {
Ed Tanous904063f2017-03-02 16:48:24 -080013616 this.$$parse(url);
13617 return true;
13618 }
13619 return false;
13620 };
13621}
13622
13623
13624/**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013625 * LocationHashbangUrl represents URL
Ed Tanous904063f2017-03-02 16:48:24 -080013626 * This object is exposed as $location service when html5 history api is enabled but the browser
13627 * does not support it.
13628 *
13629 * @constructor
13630 * @param {string} appBase application base URL
13631 * @param {string} appBaseNoFile application base URL stripped of any filename
13632 * @param {string} hashPrefix hashbang prefix
13633 */
13634function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
13635 this.$$html5 = true;
13636 LocationHashbangUrl.apply(this, arguments);
13637
13638 this.$$parseLinkUrl = function(url, relHref) {
13639 if (relHref && relHref[0] === '#') {
13640 // special case for links to hash fragments:
13641 // keep the old url and only replace the hash fragment
13642 this.hash(relHref.slice(1));
13643 return true;
13644 }
13645
13646 var rewrittenUrl;
13647 var appUrl;
13648
Ed Tanous4758d5b2017-06-06 15:28:13 -070013649 if (appBase === stripHash(url)) {
Ed Tanous904063f2017-03-02 16:48:24 -080013650 rewrittenUrl = url;
13651 } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) {
13652 rewrittenUrl = appBase + hashPrefix + appUrl;
13653 } else if (appBaseNoFile === url + '/') {
13654 rewrittenUrl = appBaseNoFile;
13655 }
13656 if (rewrittenUrl) {
13657 this.$$parse(rewrittenUrl);
13658 }
13659 return !!rewrittenUrl;
13660 };
13661
13662 this.$$compose = function() {
13663 var search = toKeyValue(this.$$search),
13664 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13665
13666 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13667 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
13668 this.$$absUrl = appBase + hashPrefix + this.$$url;
Ed Tanous4758d5b2017-06-06 15:28:13 -070013669
13670 this.$$urlUpdatedByLocation = true;
Ed Tanous904063f2017-03-02 16:48:24 -080013671 };
13672
13673}
13674
13675
13676var locationPrototype = {
13677
13678 /**
Ed Tanous4758d5b2017-06-06 15:28:13 -070013679 * Ensure absolute URL is initialized.
Ed Tanous904063f2017-03-02 16:48:24 -080013680 * @private
13681 */
13682 $$absUrl:'',
13683
13684 /**
13685 * Are we in html5 mode?
13686 * @private
13687 */
13688 $$html5: false,
13689
13690 /**
13691 * Has any change been replacing?
13692 * @private
13693 */
13694 $$replace: false,
13695
13696 /**
13697 * @ngdoc method
13698 * @name $location#absUrl
13699 *
13700 * @description
13701 * This method is getter only.
13702 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013703 * Return full URL representation with all segments encoded according to rules specified in
Ed Tanous904063f2017-03-02 16:48:24 -080013704 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
13705 *
13706 *
13707 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013708 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013709 * var absUrl = $location.absUrl();
13710 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
13711 * ```
13712 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013713 * @return {string} full URL
Ed Tanous904063f2017-03-02 16:48:24 -080013714 */
13715 absUrl: locationGetter('$$absUrl'),
13716
13717 /**
13718 * @ngdoc method
13719 * @name $location#url
13720 *
13721 * @description
13722 * This method is getter / setter.
13723 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013724 * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
Ed Tanous904063f2017-03-02 16:48:24 -080013725 *
13726 * Change path, search and hash, when called with parameter and return `$location`.
13727 *
13728 *
13729 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013730 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013731 * var url = $location.url();
13732 * // => "/some/path?foo=bar&baz=xoxo"
13733 * ```
13734 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013735 * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
Ed Tanous904063f2017-03-02 16:48:24 -080013736 * @return {string} url
13737 */
13738 url: function(url) {
13739 if (isUndefined(url)) {
13740 return this.$$url;
13741 }
13742
13743 var match = PATH_MATCH.exec(url);
13744 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
13745 if (match[2] || match[1] || url === '') this.search(match[3] || '');
13746 this.hash(match[5] || '');
13747
13748 return this;
13749 },
13750
13751 /**
13752 * @ngdoc method
13753 * @name $location#protocol
13754 *
13755 * @description
13756 * This method is getter only.
13757 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013758 * Return protocol of current URL.
Ed Tanous904063f2017-03-02 16:48:24 -080013759 *
13760 *
13761 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013762 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013763 * var protocol = $location.protocol();
13764 * // => "http"
13765 * ```
13766 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013767 * @return {string} protocol of current URL
Ed Tanous904063f2017-03-02 16:48:24 -080013768 */
13769 protocol: locationGetter('$$protocol'),
13770
13771 /**
13772 * @ngdoc method
13773 * @name $location#host
13774 *
13775 * @description
13776 * This method is getter only.
13777 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013778 * Return host of current URL.
Ed Tanous904063f2017-03-02 16:48:24 -080013779 *
13780 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
13781 *
13782 *
13783 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013784 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013785 * var host = $location.host();
13786 * // => "example.com"
13787 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013788 * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013789 * host = $location.host();
13790 * // => "example.com"
13791 * host = location.host;
13792 * // => "example.com:8080"
13793 * ```
13794 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013795 * @return {string} host of current URL.
Ed Tanous904063f2017-03-02 16:48:24 -080013796 */
13797 host: locationGetter('$$host'),
13798
13799 /**
13800 * @ngdoc method
13801 * @name $location#port
13802 *
13803 * @description
13804 * This method is getter only.
13805 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013806 * Return port of current URL.
Ed Tanous904063f2017-03-02 16:48:24 -080013807 *
13808 *
13809 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013810 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013811 * var port = $location.port();
13812 * // => 80
13813 * ```
13814 *
13815 * @return {Number} port
13816 */
13817 port: locationGetter('$$port'),
13818
13819 /**
13820 * @ngdoc method
13821 * @name $location#path
13822 *
13823 * @description
13824 * This method is getter / setter.
13825 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013826 * Return path of current URL when called without any parameter.
Ed Tanous904063f2017-03-02 16:48:24 -080013827 *
13828 * Change path when called with parameter and return `$location`.
13829 *
13830 * Note: Path should always begin with forward slash (/), this method will add the forward slash
13831 * if it is missing.
13832 *
13833 *
13834 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013835 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013836 * var path = $location.path();
13837 * // => "/some/path"
13838 * ```
13839 *
13840 * @param {(string|number)=} path New path
13841 * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
13842 */
13843 path: locationGetterSetter('$$path', function(path) {
13844 path = path !== null ? path.toString() : '';
Ed Tanous4758d5b2017-06-06 15:28:13 -070013845 return path.charAt(0) === '/' ? path : '/' + path;
Ed Tanous904063f2017-03-02 16:48:24 -080013846 }),
13847
13848 /**
13849 * @ngdoc method
13850 * @name $location#search
13851 *
13852 * @description
13853 * This method is getter / setter.
13854 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070013855 * Return search part (as object) of current URL when called without any parameter.
Ed Tanous904063f2017-03-02 16:48:24 -080013856 *
13857 * Change search part when called with parameter and return `$location`.
13858 *
13859 *
13860 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013861 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
Ed Tanous904063f2017-03-02 16:48:24 -080013862 * var searchObject = $location.search();
13863 * // => {foo: 'bar', baz: 'xoxo'}
13864 *
13865 * // set foo to 'yipee'
13866 * $location.search('foo', 'yipee');
13867 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
13868 * ```
13869 *
13870 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
13871 * hash object.
13872 *
13873 * When called with a single argument the method acts as a setter, setting the `search` component
13874 * of `$location` to the specified value.
13875 *
13876 * If the argument is a hash object containing an array of values, these values will be encoded
Ed Tanous4758d5b2017-06-06 15:28:13 -070013877 * as duplicate search parameters in the URL.
Ed Tanous904063f2017-03-02 16:48:24 -080013878 *
13879 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
13880 * will override only a single search property.
13881 *
13882 * If `paramValue` is an array, it will override the property of the `search` component of
13883 * `$location` specified via the first argument.
13884 *
13885 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
13886 *
13887 * If `paramValue` is `true`, the property specified via the first argument will be added with no
13888 * value nor trailing equal sign.
13889 *
13890 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
13891 * one or more arguments returns `$location` object itself.
13892 */
13893 search: function(search, paramValue) {
13894 switch (arguments.length) {
13895 case 0:
13896 return this.$$search;
13897 case 1:
13898 if (isString(search) || isNumber(search)) {
13899 search = search.toString();
13900 this.$$search = parseKeyValue(search);
13901 } else if (isObject(search)) {
13902 search = copy(search, {});
13903 // remove object undefined or null properties
13904 forEach(search, function(value, key) {
13905 if (value == null) delete search[key];
13906 });
13907
13908 this.$$search = search;
13909 } else {
13910 throw $locationMinErr('isrcharg',
13911 'The first argument of the `$location#search()` call must be a string or an object.');
13912 }
13913 break;
13914 default:
13915 if (isUndefined(paramValue) || paramValue === null) {
13916 delete this.$$search[search];
13917 } else {
13918 this.$$search[search] = paramValue;
13919 }
13920 }
13921
13922 this.$$compose();
13923 return this;
13924 },
13925
13926 /**
13927 * @ngdoc method
13928 * @name $location#hash
13929 *
13930 * @description
13931 * This method is getter / setter.
13932 *
13933 * Returns the hash fragment when called without any parameters.
13934 *
13935 * Changes the hash fragment when called with a parameter and returns `$location`.
13936 *
13937 *
13938 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070013939 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
Ed Tanous904063f2017-03-02 16:48:24 -080013940 * var hash = $location.hash();
13941 * // => "hashValue"
13942 * ```
13943 *
13944 * @param {(string|number)=} hash New hash fragment
13945 * @return {string} hash
13946 */
13947 hash: locationGetterSetter('$$hash', function(hash) {
13948 return hash !== null ? hash.toString() : '';
13949 }),
13950
13951 /**
13952 * @ngdoc method
13953 * @name $location#replace
13954 *
13955 * @description
13956 * If called, all changes to $location during the current `$digest` will replace the current history
13957 * record, instead of adding a new one.
13958 */
13959 replace: function() {
13960 this.$$replace = true;
13961 return this;
13962 }
13963};
13964
13965forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
13966 Location.prototype = Object.create(locationPrototype);
13967
13968 /**
13969 * @ngdoc method
13970 * @name $location#state
13971 *
13972 * @description
13973 * This method is getter / setter.
13974 *
13975 * Return the history state object when called without any parameter.
13976 *
13977 * Change the history state object when called with one parameter and return `$location`.
13978 * The state object is later passed to `pushState` or `replaceState`.
13979 *
13980 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
13981 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
13982 * older browsers (like IE9 or Android < 4.0), don't use this method.
13983 *
13984 * @param {object=} state State object for pushState or replaceState
13985 * @return {object} state
13986 */
13987 Location.prototype.state = function(state) {
13988 if (!arguments.length) {
13989 return this.$$state;
13990 }
13991
13992 if (Location !== LocationHtml5Url || !this.$$html5) {
13993 throw $locationMinErr('nostate', 'History API state support is available only ' +
13994 'in HTML5 mode and only in browsers supporting HTML5 History API');
13995 }
13996 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
13997 // but we're changing the $$state reference to $browser.state() during the $digest
13998 // so the modification window is narrow.
13999 this.$$state = isUndefined(state) ? null : state;
Ed Tanous4758d5b2017-06-06 15:28:13 -070014000 this.$$urlUpdatedByLocation = true;
Ed Tanous904063f2017-03-02 16:48:24 -080014001
14002 return this;
14003 };
14004});
14005
14006
14007function locationGetter(property) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014008 return /** @this */ function() {
Ed Tanous904063f2017-03-02 16:48:24 -080014009 return this[property];
14010 };
14011}
14012
14013
14014function locationGetterSetter(property, preprocess) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014015 return /** @this */ function(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080014016 if (isUndefined(value)) {
14017 return this[property];
14018 }
14019
14020 this[property] = preprocess(value);
14021 this.$$compose();
14022
14023 return this;
14024 };
14025}
14026
14027
14028/**
14029 * @ngdoc service
14030 * @name $location
14031 *
14032 * @requires $rootElement
14033 *
14034 * @description
14035 * The $location service parses the URL in the browser address bar (based on the
14036 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
14037 * available to your application. Changes to the URL in the address bar are reflected into
14038 * $location service and changes to $location are reflected into the browser address bar.
14039 *
14040 * **The $location service:**
14041 *
14042 * - Exposes the current URL in the browser address bar, so you can
14043 * - Watch and observe the URL.
14044 * - Change the URL.
14045 * - Synchronizes the URL with the browser when the user
14046 * - Changes the address bar.
14047 * - Clicks the back or forward button (or clicks a History link).
14048 * - Clicks on a link.
14049 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
14050 *
14051 * For more information see {@link guide/$location Developer Guide: Using $location}
14052 */
14053
14054/**
14055 * @ngdoc provider
14056 * @name $locationProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070014057 * @this
14058 *
Ed Tanous904063f2017-03-02 16:48:24 -080014059 * @description
14060 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
14061 */
14062function $LocationProvider() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014063 var hashPrefix = '!',
Ed Tanous904063f2017-03-02 16:48:24 -080014064 html5Mode = {
14065 enabled: false,
14066 requireBase: true,
14067 rewriteLinks: true
14068 };
14069
14070 /**
14071 * @ngdoc method
14072 * @name $locationProvider#hashPrefix
14073 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070014074 * The default value for the prefix is `'!'`.
Ed Tanous904063f2017-03-02 16:48:24 -080014075 * @param {string=} prefix Prefix for hash part (containing path and search)
14076 * @returns {*} current value if used as getter or itself (chaining) if used as setter
14077 */
14078 this.hashPrefix = function(prefix) {
14079 if (isDefined(prefix)) {
14080 hashPrefix = prefix;
14081 return this;
14082 } else {
14083 return hashPrefix;
14084 }
14085 };
14086
14087 /**
14088 * @ngdoc method
14089 * @name $locationProvider#html5Mode
14090 * @description
14091 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
14092 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
14093 * properties:
14094 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
14095 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
14096 * support `pushState`.
14097 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
14098 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
14099 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
14100 * See the {@link guide/$location $location guide for more information}
Ed Tanous4758d5b2017-06-06 15:28:13 -070014101 * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
14102 * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
14103 * only happen on links with an attribute that matches the given string. For example, if set
14104 * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
14105 * Note that [attribute name normalization](guide/directive#normalization) does not apply
14106 * here, so `'internalLink'` will **not** match `'internal-link'`.
Ed Tanous904063f2017-03-02 16:48:24 -080014107 *
14108 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
14109 */
14110 this.html5Mode = function(mode) {
14111 if (isBoolean(mode)) {
14112 html5Mode.enabled = mode;
14113 return this;
14114 } else if (isObject(mode)) {
14115
14116 if (isBoolean(mode.enabled)) {
14117 html5Mode.enabled = mode.enabled;
14118 }
14119
14120 if (isBoolean(mode.requireBase)) {
14121 html5Mode.requireBase = mode.requireBase;
14122 }
14123
Ed Tanous4758d5b2017-06-06 15:28:13 -070014124 if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
Ed Tanous904063f2017-03-02 16:48:24 -080014125 html5Mode.rewriteLinks = mode.rewriteLinks;
14126 }
14127
14128 return this;
14129 } else {
14130 return html5Mode;
14131 }
14132 };
14133
14134 /**
14135 * @ngdoc event
14136 * @name $location#$locationChangeStart
14137 * @eventType broadcast on root scope
14138 * @description
14139 * Broadcasted before a URL will change.
14140 *
14141 * This change can be prevented by calling
14142 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
14143 * details about event object. Upon successful change
14144 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
14145 *
14146 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
14147 * the browser supports the HTML5 History API.
14148 *
14149 * @param {Object} angularEvent Synthetic event object.
14150 * @param {string} newUrl New URL
14151 * @param {string=} oldUrl URL that was before it was changed.
14152 * @param {string=} newState New history state object
14153 * @param {string=} oldState History state object that was before it was changed.
14154 */
14155
14156 /**
14157 * @ngdoc event
14158 * @name $location#$locationChangeSuccess
14159 * @eventType broadcast on root scope
14160 * @description
14161 * Broadcasted after a URL was changed.
14162 *
14163 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
14164 * the browser supports the HTML5 History API.
14165 *
14166 * @param {Object} angularEvent Synthetic event object.
14167 * @param {string} newUrl New URL
14168 * @param {string=} oldUrl URL that was before it was changed.
14169 * @param {string=} newState New history state object
14170 * @param {string=} oldState History state object that was before it was changed.
14171 */
14172
14173 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
14174 function($rootScope, $browser, $sniffer, $rootElement, $window) {
14175 var $location,
14176 LocationMode,
14177 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
14178 initialUrl = $browser.url(),
14179 appBase;
14180
14181 if (html5Mode.enabled) {
14182 if (!baseHref && html5Mode.requireBase) {
14183 throw $locationMinErr('nobase',
Ed Tanous4758d5b2017-06-06 15:28:13 -070014184 '$location in HTML5 mode requires a <base> tag to be present!');
Ed Tanous904063f2017-03-02 16:48:24 -080014185 }
14186 appBase = serverBase(initialUrl) + (baseHref || '/');
14187 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
14188 } else {
14189 appBase = stripHash(initialUrl);
14190 LocationMode = LocationHashbangUrl;
14191 }
14192 var appBaseNoFile = stripFile(appBase);
14193
14194 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
14195 $location.$$parseLinkUrl(initialUrl, initialUrl);
14196
14197 $location.$$state = $browser.state();
14198
14199 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
14200
14201 function setBrowserUrlWithFallback(url, replace, state) {
14202 var oldUrl = $location.url();
14203 var oldState = $location.$$state;
14204 try {
14205 $browser.url(url, replace, state);
14206
14207 // Make sure $location.state() returns referentially identical (not just deeply equal)
14208 // state object; this makes possible quick checking if the state changed in the digest
14209 // loop. Checking deep equality would be too expensive.
14210 $location.$$state = $browser.state();
14211 } catch (e) {
14212 // Restore old values if pushState fails
14213 $location.url(oldUrl);
14214 $location.$$state = oldState;
14215
14216 throw e;
14217 }
14218 }
14219
14220 $rootElement.on('click', function(event) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014221 var rewriteLinks = html5Mode.rewriteLinks;
Ed Tanous904063f2017-03-02 16:48:24 -080014222 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
14223 // currently we open nice url link and redirect then
14224
Ed Tanous4758d5b2017-06-06 15:28:13 -070014225 if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;
Ed Tanous904063f2017-03-02 16:48:24 -080014226
14227 var elm = jqLite(event.target);
14228
14229 // traverse the DOM up to find first A tag
14230 while (nodeName_(elm[0]) !== 'a') {
14231 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
14232 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
14233 }
14234
Ed Tanous4758d5b2017-06-06 15:28:13 -070014235 if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return;
14236
Ed Tanous904063f2017-03-02 16:48:24 -080014237 var absHref = elm.prop('href');
14238 // get the actual href attribute - see
14239 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
14240 var relHref = elm.attr('href') || elm.attr('xlink:href');
14241
14242 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
14243 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
14244 // an animation.
14245 absHref = urlResolve(absHref.animVal).href;
14246 }
14247
14248 // Ignore when url is started with javascript: or mailto:
14249 if (IGNORE_URI_REGEXP.test(absHref)) return;
14250
14251 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
14252 if ($location.$$parseLinkUrl(absHref, relHref)) {
14253 // We do a preventDefault for all urls that are part of the angular application,
14254 // in html5mode and also without, so that we are able to abort navigation without
14255 // getting double entries in the location history.
14256 event.preventDefault();
14257 // update location manually
Ed Tanous4758d5b2017-06-06 15:28:13 -070014258 if ($location.absUrl() !== $browser.url()) {
Ed Tanous904063f2017-03-02 16:48:24 -080014259 $rootScope.$apply();
14260 // hack to work around FF6 bug 684208 when scenario runner clicks on links
14261 $window.angular['ff-684208-preventDefault'] = true;
14262 }
14263 }
14264 }
14265 });
14266
14267
14268 // rewrite hashbang url <> html5 url
Ed Tanous4758d5b2017-06-06 15:28:13 -070014269 if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
Ed Tanous904063f2017-03-02 16:48:24 -080014270 $browser.url($location.absUrl(), true);
14271 }
14272
14273 var initializing = true;
14274
14275 // update $location when $browser url changes
14276 $browser.onUrlChange(function(newUrl, newState) {
14277
Ed Tanous4758d5b2017-06-06 15:28:13 -070014278 if (!startsWith(newUrl, appBaseNoFile)) {
Ed Tanous904063f2017-03-02 16:48:24 -080014279 // If we are navigating outside of the app then force a reload
14280 $window.location.href = newUrl;
14281 return;
14282 }
14283
14284 $rootScope.$evalAsync(function() {
14285 var oldUrl = $location.absUrl();
14286 var oldState = $location.$$state;
14287 var defaultPrevented;
14288 newUrl = trimEmptyHash(newUrl);
14289 $location.$$parse(newUrl);
14290 $location.$$state = newState;
14291
14292 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
14293 newState, oldState).defaultPrevented;
14294
14295 // if the location was changed by a `$locationChangeStart` handler then stop
14296 // processing this location change
14297 if ($location.absUrl() !== newUrl) return;
14298
14299 if (defaultPrevented) {
14300 $location.$$parse(oldUrl);
14301 $location.$$state = oldState;
14302 setBrowserUrlWithFallback(oldUrl, false, oldState);
14303 } else {
14304 initializing = false;
14305 afterLocationChange(oldUrl, oldState);
14306 }
14307 });
14308 if (!$rootScope.$$phase) $rootScope.$digest();
14309 });
14310
14311 // update browser
14312 $rootScope.$watch(function $locationWatch() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014313 if (initializing || $location.$$urlUpdatedByLocation) {
14314 $location.$$urlUpdatedByLocation = false;
Ed Tanous904063f2017-03-02 16:48:24 -080014315
Ed Tanous4758d5b2017-06-06 15:28:13 -070014316 var oldUrl = trimEmptyHash($browser.url());
14317 var newUrl = trimEmptyHash($location.absUrl());
14318 var oldState = $browser.state();
14319 var currentReplace = $location.$$replace;
14320 var urlOrStateChanged = oldUrl !== newUrl ||
14321 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
Ed Tanous904063f2017-03-02 16:48:24 -080014322
Ed Tanous4758d5b2017-06-06 15:28:13 -070014323 if (initializing || urlOrStateChanged) {
14324 initializing = false;
Ed Tanous904063f2017-03-02 16:48:24 -080014325
Ed Tanous4758d5b2017-06-06 15:28:13 -070014326 $rootScope.$evalAsync(function() {
14327 var newUrl = $location.absUrl();
14328 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
14329 $location.$$state, oldState).defaultPrevented;
Ed Tanous904063f2017-03-02 16:48:24 -080014330
Ed Tanous4758d5b2017-06-06 15:28:13 -070014331 // if the location was changed by a `$locationChangeStart` handler then stop
14332 // processing this location change
14333 if ($location.absUrl() !== newUrl) return;
14334
14335 if (defaultPrevented) {
14336 $location.$$parse(oldUrl);
14337 $location.$$state = oldState;
14338 } else {
14339 if (urlOrStateChanged) {
14340 setBrowserUrlWithFallback(newUrl, currentReplace,
14341 oldState === $location.$$state ? null : $location.$$state);
14342 }
14343 afterLocationChange(oldUrl, oldState);
Ed Tanous904063f2017-03-02 16:48:24 -080014344 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070014345 });
14346 }
Ed Tanous904063f2017-03-02 16:48:24 -080014347 }
14348
14349 $location.$$replace = false;
14350
14351 // we don't need to return anything because $evalAsync will make the digest loop dirty when
14352 // there is a change
14353 });
14354
14355 return $location;
14356
14357 function afterLocationChange(oldUrl, oldState) {
14358 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
14359 $location.$$state, oldState);
14360 }
14361}];
14362}
14363
14364/**
14365 * @ngdoc service
14366 * @name $log
14367 * @requires $window
14368 *
14369 * @description
14370 * Simple service for logging. Default implementation safely writes the message
14371 * into the browser's console (if present).
14372 *
14373 * The main purpose of this service is to simplify debugging and troubleshooting.
14374 *
14375 * The default is to log `debug` messages. You can use
14376 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
14377 *
14378 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070014379 <example module="logExample" name="log-service">
Ed Tanous904063f2017-03-02 16:48:24 -080014380 <file name="script.js">
14381 angular.module('logExample', [])
14382 .controller('LogController', ['$scope', '$log', function($scope, $log) {
14383 $scope.$log = $log;
14384 $scope.message = 'Hello World!';
14385 }]);
14386 </file>
14387 <file name="index.html">
14388 <div ng-controller="LogController">
14389 <p>Reload this page with open console, enter text and hit the log button...</p>
14390 <label>Message:
14391 <input type="text" ng-model="message" /></label>
14392 <button ng-click="$log.log(message)">log</button>
14393 <button ng-click="$log.warn(message)">warn</button>
14394 <button ng-click="$log.info(message)">info</button>
14395 <button ng-click="$log.error(message)">error</button>
14396 <button ng-click="$log.debug(message)">debug</button>
14397 </div>
14398 </file>
14399 </example>
14400 */
14401
14402/**
14403 * @ngdoc provider
14404 * @name $logProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070014405 * @this
14406 *
Ed Tanous904063f2017-03-02 16:48:24 -080014407 * @description
14408 * Use the `$logProvider` to configure how the application logs messages
14409 */
14410function $LogProvider() {
14411 var debug = true,
14412 self = this;
14413
14414 /**
14415 * @ngdoc method
14416 * @name $logProvider#debugEnabled
14417 * @description
14418 * @param {boolean=} flag enable or disable debug level messages
14419 * @returns {*} current value if used as getter or itself (chaining) if used as setter
14420 */
14421 this.debugEnabled = function(flag) {
14422 if (isDefined(flag)) {
14423 debug = flag;
Ed Tanous4758d5b2017-06-06 15:28:13 -070014424 return this;
Ed Tanous904063f2017-03-02 16:48:24 -080014425 } else {
14426 return debug;
14427 }
14428 };
14429
14430 this.$get = ['$window', function($window) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014431 // Support: IE 9-11, Edge 12-14+
14432 // IE/Edge display errors in such a way that it requires the user to click in 4 places
14433 // to see the stack trace. There is no way to feature-detect it so there's a chance
14434 // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't
14435 // break apps. Other browsers display errors in a sensible way and some of them map stack
14436 // traces along source maps if available so it makes sense to let browsers display it
14437 // as they want.
14438 var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent);
14439
Ed Tanous904063f2017-03-02 16:48:24 -080014440 return {
14441 /**
14442 * @ngdoc method
14443 * @name $log#log
14444 *
14445 * @description
14446 * Write a log message
14447 */
14448 log: consoleLog('log'),
14449
14450 /**
14451 * @ngdoc method
14452 * @name $log#info
14453 *
14454 * @description
14455 * Write an information message
14456 */
14457 info: consoleLog('info'),
14458
14459 /**
14460 * @ngdoc method
14461 * @name $log#warn
14462 *
14463 * @description
14464 * Write a warning message
14465 */
14466 warn: consoleLog('warn'),
14467
14468 /**
14469 * @ngdoc method
14470 * @name $log#error
14471 *
14472 * @description
14473 * Write an error message
14474 */
14475 error: consoleLog('error'),
14476
14477 /**
14478 * @ngdoc method
14479 * @name $log#debug
14480 *
14481 * @description
14482 * Write a debug message
14483 */
14484 debug: (function() {
14485 var fn = consoleLog('debug');
14486
14487 return function() {
14488 if (debug) {
14489 fn.apply(self, arguments);
14490 }
14491 };
Ed Tanous4758d5b2017-06-06 15:28:13 -070014492 })()
Ed Tanous904063f2017-03-02 16:48:24 -080014493 };
14494
14495 function formatError(arg) {
14496 if (arg instanceof Error) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014497 if (arg.stack && formatStackTrace) {
Ed Tanous904063f2017-03-02 16:48:24 -080014498 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
14499 ? 'Error: ' + arg.message + '\n' + arg.stack
14500 : arg.stack;
14501 } else if (arg.sourceURL) {
14502 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
14503 }
14504 }
14505 return arg;
14506 }
14507
14508 function consoleLog(type) {
14509 var console = $window.console || {},
14510 logFn = console[type] || console.log || noop,
14511 hasApply = false;
14512
14513 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
14514 // The reason behind this is that console.log has type "object" in IE8...
14515 try {
14516 hasApply = !!logFn.apply;
Ed Tanous4758d5b2017-06-06 15:28:13 -070014517 } catch (e) { /* empty */ }
Ed Tanous904063f2017-03-02 16:48:24 -080014518
14519 if (hasApply) {
14520 return function() {
14521 var args = [];
14522 forEach(arguments, function(arg) {
14523 args.push(formatError(arg));
14524 });
14525 return logFn.apply(console, args);
14526 };
14527 }
14528
14529 // we are IE which either doesn't have window.console => this is noop and we do nothing,
14530 // or we are IE where console.log doesn't have apply so we log at least first 2 args
14531 return function(arg1, arg2) {
14532 logFn(arg1, arg2 == null ? '' : arg2);
14533 };
14534 }
14535 }];
14536}
14537
14538/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14539 * Any commits to this file should be reviewed with security in mind. *
14540 * Changes to this file can potentially create security vulnerabilities. *
14541 * An approval from 2 Core members with history of modifying *
14542 * this file is required. *
14543 * *
14544 * Does the change somehow allow for arbitrary javascript to be executed? *
14545 * Or allows for someone to change the prototype of built-in objects? *
14546 * Or gives undesired access to variables likes document or window? *
14547 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
14548
14549var $parseMinErr = minErr('$parse');
14550
Ed Tanous4758d5b2017-06-06 15:28:13 -070014551var objectValueOf = {}.constructor.prototype.valueOf;
14552
Ed Tanous904063f2017-03-02 16:48:24 -080014553// Sandboxing Angular Expressions
14554// ------------------------------
Ed Tanous4758d5b2017-06-06 15:28:13 -070014555// Angular expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by
14556// various means such as obtaining a reference to native JS functions like the Function constructor.
Ed Tanous904063f2017-03-02 16:48:24 -080014557//
14558// As an example, consider the following Angular expression:
14559//
14560// {}.toString.constructor('alert("evil JS code")')
14561//
Ed Tanous4758d5b2017-06-06 15:28:13 -070014562// It is important to realize that if you create an expression from a string that contains user provided
14563// content then it is possible that your application contains a security vulnerability to an XSS style attack.
Ed Tanous904063f2017-03-02 16:48:24 -080014564//
14565// See https://docs.angularjs.org/guide/security
14566
14567
Ed Tanous904063f2017-03-02 16:48:24 -080014568function getStringValue(name) {
14569 // Property names must be strings. This means that non-string objects cannot be used
14570 // as keys in an object. Any non-string object, including a number, is typecasted
14571 // into a string via the toString method.
14572 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names
14573 //
14574 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it
14575 // to a string. It's not always possible. If `name` is an object and its `toString` method is
14576 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown:
14577 //
14578 // TypeError: Cannot convert object to primitive value
14579 //
14580 // For performance reasons, we don't catch this error here and allow it to propagate up the call
14581 // stack. Note that you'll get the same error in JavaScript if you try to access a property using
14582 // such a 'broken' object as a key.
14583 return name + '';
14584}
14585
Ed Tanous904063f2017-03-02 16:48:24 -080014586
14587var OPERATORS = createMap();
14588forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
Ed Tanous4758d5b2017-06-06 15:28:13 -070014589var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'};
Ed Tanous904063f2017-03-02 16:48:24 -080014590
14591
14592/////////////////////////////////////////
14593
14594
14595/**
14596 * @constructor
14597 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070014598var Lexer = function Lexer(options) {
Ed Tanous904063f2017-03-02 16:48:24 -080014599 this.options = options;
14600};
14601
14602Lexer.prototype = {
14603 constructor: Lexer,
14604
14605 lex: function(text) {
14606 this.text = text;
14607 this.index = 0;
14608 this.tokens = [];
14609
14610 while (this.index < this.text.length) {
14611 var ch = this.text.charAt(this.index);
Ed Tanous4758d5b2017-06-06 15:28:13 -070014612 if (ch === '"' || ch === '\'') {
Ed Tanous904063f2017-03-02 16:48:24 -080014613 this.readString(ch);
14614 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
14615 this.readNumber();
14616 } else if (this.isIdentifierStart(this.peekMultichar())) {
14617 this.readIdent();
14618 } else if (this.is(ch, '(){}[].,;:?')) {
14619 this.tokens.push({index: this.index, text: ch});
14620 this.index++;
14621 } else if (this.isWhitespace(ch)) {
14622 this.index++;
14623 } else {
14624 var ch2 = ch + this.peek();
14625 var ch3 = ch2 + this.peek(2);
14626 var op1 = OPERATORS[ch];
14627 var op2 = OPERATORS[ch2];
14628 var op3 = OPERATORS[ch3];
14629 if (op1 || op2 || op3) {
14630 var token = op3 ? ch3 : (op2 ? ch2 : ch);
14631 this.tokens.push({index: this.index, text: token, operator: true});
14632 this.index += token.length;
14633 } else {
14634 this.throwError('Unexpected next character ', this.index, this.index + 1);
14635 }
14636 }
14637 }
14638 return this.tokens;
14639 },
14640
14641 is: function(ch, chars) {
14642 return chars.indexOf(ch) !== -1;
14643 },
14644
14645 peek: function(i) {
14646 var num = i || 1;
14647 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
14648 },
14649
14650 isNumber: function(ch) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014651 return ('0' <= ch && ch <= '9') && typeof ch === 'string';
Ed Tanous904063f2017-03-02 16:48:24 -080014652 },
14653
14654 isWhitespace: function(ch) {
14655 // IE treats non-breaking space as \u00A0
14656 return (ch === ' ' || ch === '\r' || ch === '\t' ||
14657 ch === '\n' || ch === '\v' || ch === '\u00A0');
14658 },
14659
14660 isIdentifierStart: function(ch) {
14661 return this.options.isIdentifierStart ?
14662 this.options.isIdentifierStart(ch, this.codePointAt(ch)) :
14663 this.isValidIdentifierStart(ch);
14664 },
14665
14666 isValidIdentifierStart: function(ch) {
14667 return ('a' <= ch && ch <= 'z' ||
14668 'A' <= ch && ch <= 'Z' ||
14669 '_' === ch || ch === '$');
14670 },
14671
14672 isIdentifierContinue: function(ch) {
14673 return this.options.isIdentifierContinue ?
14674 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) :
14675 this.isValidIdentifierContinue(ch);
14676 },
14677
14678 isValidIdentifierContinue: function(ch, cp) {
14679 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch);
14680 },
14681
14682 codePointAt: function(ch) {
14683 if (ch.length === 1) return ch.charCodeAt(0);
Ed Tanous4758d5b2017-06-06 15:28:13 -070014684 // eslint-disable-next-line no-bitwise
Ed Tanous904063f2017-03-02 16:48:24 -080014685 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
Ed Tanous904063f2017-03-02 16:48:24 -080014686 },
14687
14688 peekMultichar: function() {
14689 var ch = this.text.charAt(this.index);
14690 var peek = this.peek();
14691 if (!peek) {
14692 return ch;
14693 }
14694 var cp1 = ch.charCodeAt(0);
14695 var cp2 = peek.charCodeAt(0);
14696 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
14697 return ch + peek;
14698 }
14699 return ch;
14700 },
14701
14702 isExpOperator: function(ch) {
14703 return (ch === '-' || ch === '+' || this.isNumber(ch));
14704 },
14705
14706 throwError: function(error, start, end) {
14707 end = end || this.index;
14708 var colStr = (isDefined(start)
14709 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
14710 : ' ' + end);
14711 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
14712 error, colStr, this.text);
14713 },
14714
14715 readNumber: function() {
14716 var number = '';
14717 var start = this.index;
14718 while (this.index < this.text.length) {
14719 var ch = lowercase(this.text.charAt(this.index));
Ed Tanous4758d5b2017-06-06 15:28:13 -070014720 if (ch === '.' || this.isNumber(ch)) {
Ed Tanous904063f2017-03-02 16:48:24 -080014721 number += ch;
14722 } else {
14723 var peekCh = this.peek();
Ed Tanous4758d5b2017-06-06 15:28:13 -070014724 if (ch === 'e' && this.isExpOperator(peekCh)) {
Ed Tanous904063f2017-03-02 16:48:24 -080014725 number += ch;
14726 } else if (this.isExpOperator(ch) &&
14727 peekCh && this.isNumber(peekCh) &&
Ed Tanous4758d5b2017-06-06 15:28:13 -070014728 number.charAt(number.length - 1) === 'e') {
Ed Tanous904063f2017-03-02 16:48:24 -080014729 number += ch;
14730 } else if (this.isExpOperator(ch) &&
14731 (!peekCh || !this.isNumber(peekCh)) &&
Ed Tanous4758d5b2017-06-06 15:28:13 -070014732 number.charAt(number.length - 1) === 'e') {
Ed Tanous904063f2017-03-02 16:48:24 -080014733 this.throwError('Invalid exponent');
14734 } else {
14735 break;
14736 }
14737 }
14738 this.index++;
14739 }
14740 this.tokens.push({
14741 index: start,
14742 text: number,
14743 constant: true,
14744 value: Number(number)
14745 });
14746 },
14747
14748 readIdent: function() {
14749 var start = this.index;
14750 this.index += this.peekMultichar().length;
14751 while (this.index < this.text.length) {
14752 var ch = this.peekMultichar();
14753 if (!this.isIdentifierContinue(ch)) {
14754 break;
14755 }
14756 this.index += ch.length;
14757 }
14758 this.tokens.push({
14759 index: start,
14760 text: this.text.slice(start, this.index),
14761 identifier: true
14762 });
14763 },
14764
14765 readString: function(quote) {
14766 var start = this.index;
14767 this.index++;
14768 var string = '';
14769 var rawString = quote;
14770 var escape = false;
14771 while (this.index < this.text.length) {
14772 var ch = this.text.charAt(this.index);
14773 rawString += ch;
14774 if (escape) {
14775 if (ch === 'u') {
14776 var hex = this.text.substring(this.index + 1, this.index + 5);
14777 if (!hex.match(/[\da-f]{4}/i)) {
14778 this.throwError('Invalid unicode escape [\\u' + hex + ']');
14779 }
14780 this.index += 4;
14781 string += String.fromCharCode(parseInt(hex, 16));
14782 } else {
14783 var rep = ESCAPE[ch];
14784 string = string + (rep || ch);
14785 }
14786 escape = false;
14787 } else if (ch === '\\') {
14788 escape = true;
14789 } else if (ch === quote) {
14790 this.index++;
14791 this.tokens.push({
14792 index: start,
14793 text: rawString,
14794 constant: true,
14795 value: string
14796 });
14797 return;
14798 } else {
14799 string += ch;
14800 }
14801 this.index++;
14802 }
14803 this.throwError('Unterminated quote', start);
14804 }
14805};
14806
Ed Tanous4758d5b2017-06-06 15:28:13 -070014807var AST = function AST(lexer, options) {
Ed Tanous904063f2017-03-02 16:48:24 -080014808 this.lexer = lexer;
14809 this.options = options;
14810};
14811
14812AST.Program = 'Program';
14813AST.ExpressionStatement = 'ExpressionStatement';
14814AST.AssignmentExpression = 'AssignmentExpression';
14815AST.ConditionalExpression = 'ConditionalExpression';
14816AST.LogicalExpression = 'LogicalExpression';
14817AST.BinaryExpression = 'BinaryExpression';
14818AST.UnaryExpression = 'UnaryExpression';
14819AST.CallExpression = 'CallExpression';
14820AST.MemberExpression = 'MemberExpression';
14821AST.Identifier = 'Identifier';
14822AST.Literal = 'Literal';
14823AST.ArrayExpression = 'ArrayExpression';
14824AST.Property = 'Property';
14825AST.ObjectExpression = 'ObjectExpression';
14826AST.ThisExpression = 'ThisExpression';
14827AST.LocalsExpression = 'LocalsExpression';
14828
14829// Internal use only
14830AST.NGValueParameter = 'NGValueParameter';
14831
14832AST.prototype = {
14833 ast: function(text) {
14834 this.text = text;
14835 this.tokens = this.lexer.lex(text);
14836
14837 var value = this.program();
14838
14839 if (this.tokens.length !== 0) {
14840 this.throwError('is an unexpected token', this.tokens[0]);
14841 }
14842
14843 return value;
14844 },
14845
14846 program: function() {
14847 var body = [];
14848 while (true) {
14849 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
14850 body.push(this.expressionStatement());
14851 if (!this.expect(';')) {
14852 return { type: AST.Program, body: body};
14853 }
14854 }
14855 },
14856
14857 expressionStatement: function() {
14858 return { type: AST.ExpressionStatement, expression: this.filterChain() };
14859 },
14860
14861 filterChain: function() {
14862 var left = this.expression();
Ed Tanous4758d5b2017-06-06 15:28:13 -070014863 while (this.expect('|')) {
Ed Tanous904063f2017-03-02 16:48:24 -080014864 left = this.filter(left);
14865 }
14866 return left;
14867 },
14868
14869 expression: function() {
14870 return this.assignment();
14871 },
14872
14873 assignment: function() {
14874 var result = this.ternary();
14875 if (this.expect('=')) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070014876 if (!isAssignable(result)) {
14877 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value');
14878 }
14879
Ed Tanous904063f2017-03-02 16:48:24 -080014880 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
14881 }
14882 return result;
14883 },
14884
14885 ternary: function() {
14886 var test = this.logicalOR();
14887 var alternate;
14888 var consequent;
14889 if (this.expect('?')) {
14890 alternate = this.expression();
14891 if (this.consume(':')) {
14892 consequent = this.expression();
14893 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
14894 }
14895 }
14896 return test;
14897 },
14898
14899 logicalOR: function() {
14900 var left = this.logicalAND();
14901 while (this.expect('||')) {
14902 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
14903 }
14904 return left;
14905 },
14906
14907 logicalAND: function() {
14908 var left = this.equality();
14909 while (this.expect('&&')) {
14910 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
14911 }
14912 return left;
14913 },
14914
14915 equality: function() {
14916 var left = this.relational();
14917 var token;
14918 while ((token = this.expect('==','!=','===','!=='))) {
14919 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
14920 }
14921 return left;
14922 },
14923
14924 relational: function() {
14925 var left = this.additive();
14926 var token;
14927 while ((token = this.expect('<', '>', '<=', '>='))) {
14928 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
14929 }
14930 return left;
14931 },
14932
14933 additive: function() {
14934 var left = this.multiplicative();
14935 var token;
14936 while ((token = this.expect('+','-'))) {
14937 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
14938 }
14939 return left;
14940 },
14941
14942 multiplicative: function() {
14943 var left = this.unary();
14944 var token;
14945 while ((token = this.expect('*','/','%'))) {
14946 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
14947 }
14948 return left;
14949 },
14950
14951 unary: function() {
14952 var token;
14953 if ((token = this.expect('+', '-', '!'))) {
14954 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
14955 } else {
14956 return this.primary();
14957 }
14958 },
14959
14960 primary: function() {
14961 var primary;
14962 if (this.expect('(')) {
14963 primary = this.filterChain();
14964 this.consume(')');
14965 } else if (this.expect('[')) {
14966 primary = this.arrayDeclaration();
14967 } else if (this.expect('{')) {
14968 primary = this.object();
14969 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) {
14970 primary = copy(this.selfReferential[this.consume().text]);
14971 } else if (this.options.literals.hasOwnProperty(this.peek().text)) {
14972 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]};
14973 } else if (this.peek().identifier) {
14974 primary = this.identifier();
14975 } else if (this.peek().constant) {
14976 primary = this.constant();
14977 } else {
14978 this.throwError('not a primary expression', this.peek());
14979 }
14980
14981 var next;
14982 while ((next = this.expect('(', '[', '.'))) {
14983 if (next.text === '(') {
14984 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
14985 this.consume(')');
14986 } else if (next.text === '[') {
14987 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
14988 this.consume(']');
14989 } else if (next.text === '.') {
14990 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
14991 } else {
14992 this.throwError('IMPOSSIBLE');
14993 }
14994 }
14995 return primary;
14996 },
14997
14998 filter: function(baseExpression) {
14999 var args = [baseExpression];
15000 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
15001
15002 while (this.expect(':')) {
15003 args.push(this.expression());
15004 }
15005
15006 return result;
15007 },
15008
15009 parseArguments: function() {
15010 var args = [];
15011 if (this.peekToken().text !== ')') {
15012 do {
15013 args.push(this.filterChain());
15014 } while (this.expect(','));
15015 }
15016 return args;
15017 },
15018
15019 identifier: function() {
15020 var token = this.consume();
15021 if (!token.identifier) {
15022 this.throwError('is not a valid identifier', token);
15023 }
15024 return { type: AST.Identifier, name: token.text };
15025 },
15026
15027 constant: function() {
15028 // TODO check that it is a constant
15029 return { type: AST.Literal, value: this.consume().value };
15030 },
15031
15032 arrayDeclaration: function() {
15033 var elements = [];
15034 if (this.peekToken().text !== ']') {
15035 do {
15036 if (this.peek(']')) {
15037 // Support trailing commas per ES5.1.
15038 break;
15039 }
15040 elements.push(this.expression());
15041 } while (this.expect(','));
15042 }
15043 this.consume(']');
15044
15045 return { type: AST.ArrayExpression, elements: elements };
15046 },
15047
15048 object: function() {
15049 var properties = [], property;
15050 if (this.peekToken().text !== '}') {
15051 do {
15052 if (this.peek('}')) {
15053 // Support trailing commas per ES5.1.
15054 break;
15055 }
15056 property = {type: AST.Property, kind: 'init'};
15057 if (this.peek().constant) {
15058 property.key = this.constant();
15059 property.computed = false;
15060 this.consume(':');
15061 property.value = this.expression();
15062 } else if (this.peek().identifier) {
15063 property.key = this.identifier();
15064 property.computed = false;
15065 if (this.peek(':')) {
15066 this.consume(':');
15067 property.value = this.expression();
15068 } else {
15069 property.value = property.key;
15070 }
15071 } else if (this.peek('[')) {
15072 this.consume('[');
15073 property.key = this.expression();
15074 this.consume(']');
15075 property.computed = true;
15076 this.consume(':');
15077 property.value = this.expression();
15078 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015079 this.throwError('invalid key', this.peek());
Ed Tanous904063f2017-03-02 16:48:24 -080015080 }
15081 properties.push(property);
15082 } while (this.expect(','));
15083 }
15084 this.consume('}');
15085
15086 return {type: AST.ObjectExpression, properties: properties };
15087 },
15088
15089 throwError: function(msg, token) {
15090 throw $parseMinErr('syntax',
15091 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
15092 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
15093 },
15094
15095 consume: function(e1) {
15096 if (this.tokens.length === 0) {
15097 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
15098 }
15099
15100 var token = this.expect(e1);
15101 if (!token) {
15102 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
15103 }
15104 return token;
15105 },
15106
15107 peekToken: function() {
15108 if (this.tokens.length === 0) {
15109 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
15110 }
15111 return this.tokens[0];
15112 },
15113
15114 peek: function(e1, e2, e3, e4) {
15115 return this.peekAhead(0, e1, e2, e3, e4);
15116 },
15117
15118 peekAhead: function(i, e1, e2, e3, e4) {
15119 if (this.tokens.length > i) {
15120 var token = this.tokens[i];
15121 var t = token.text;
15122 if (t === e1 || t === e2 || t === e3 || t === e4 ||
15123 (!e1 && !e2 && !e3 && !e4)) {
15124 return token;
15125 }
15126 }
15127 return false;
15128 },
15129
15130 expect: function(e1, e2, e3, e4) {
15131 var token = this.peek(e1, e2, e3, e4);
15132 if (token) {
15133 this.tokens.shift();
15134 return token;
15135 }
15136 return false;
15137 },
15138
15139 selfReferential: {
15140 'this': {type: AST.ThisExpression },
15141 '$locals': {type: AST.LocalsExpression }
15142 }
15143};
15144
15145function ifDefined(v, d) {
15146 return typeof v !== 'undefined' ? v : d;
15147}
15148
15149function plusFn(l, r) {
15150 if (typeof l === 'undefined') return r;
15151 if (typeof r === 'undefined') return l;
15152 return l + r;
15153}
15154
15155function isStateless($filter, filterName) {
15156 var fn = $filter(filterName);
15157 return !fn.$stateful;
15158}
15159
15160function findConstantAndWatchExpressions(ast, $filter) {
15161 var allConstants;
15162 var argsToWatch;
Ed Tanous4758d5b2017-06-06 15:28:13 -070015163 var isStatelessFilter;
Ed Tanous904063f2017-03-02 16:48:24 -080015164 switch (ast.type) {
15165 case AST.Program:
15166 allConstants = true;
15167 forEach(ast.body, function(expr) {
15168 findConstantAndWatchExpressions(expr.expression, $filter);
15169 allConstants = allConstants && expr.expression.constant;
15170 });
15171 ast.constant = allConstants;
15172 break;
15173 case AST.Literal:
15174 ast.constant = true;
15175 ast.toWatch = [];
15176 break;
15177 case AST.UnaryExpression:
15178 findConstantAndWatchExpressions(ast.argument, $filter);
15179 ast.constant = ast.argument.constant;
15180 ast.toWatch = ast.argument.toWatch;
15181 break;
15182 case AST.BinaryExpression:
15183 findConstantAndWatchExpressions(ast.left, $filter);
15184 findConstantAndWatchExpressions(ast.right, $filter);
15185 ast.constant = ast.left.constant && ast.right.constant;
15186 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
15187 break;
15188 case AST.LogicalExpression:
15189 findConstantAndWatchExpressions(ast.left, $filter);
15190 findConstantAndWatchExpressions(ast.right, $filter);
15191 ast.constant = ast.left.constant && ast.right.constant;
15192 ast.toWatch = ast.constant ? [] : [ast];
15193 break;
15194 case AST.ConditionalExpression:
15195 findConstantAndWatchExpressions(ast.test, $filter);
15196 findConstantAndWatchExpressions(ast.alternate, $filter);
15197 findConstantAndWatchExpressions(ast.consequent, $filter);
15198 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
15199 ast.toWatch = ast.constant ? [] : [ast];
15200 break;
15201 case AST.Identifier:
15202 ast.constant = false;
15203 ast.toWatch = [ast];
15204 break;
15205 case AST.MemberExpression:
15206 findConstantAndWatchExpressions(ast.object, $filter);
15207 if (ast.computed) {
15208 findConstantAndWatchExpressions(ast.property, $filter);
15209 }
15210 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
15211 ast.toWatch = [ast];
15212 break;
15213 case AST.CallExpression:
Ed Tanous4758d5b2017-06-06 15:28:13 -070015214 isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false;
15215 allConstants = isStatelessFilter;
Ed Tanous904063f2017-03-02 16:48:24 -080015216 argsToWatch = [];
15217 forEach(ast.arguments, function(expr) {
15218 findConstantAndWatchExpressions(expr, $filter);
15219 allConstants = allConstants && expr.constant;
15220 if (!expr.constant) {
15221 argsToWatch.push.apply(argsToWatch, expr.toWatch);
15222 }
15223 });
15224 ast.constant = allConstants;
Ed Tanous4758d5b2017-06-06 15:28:13 -070015225 ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
Ed Tanous904063f2017-03-02 16:48:24 -080015226 break;
15227 case AST.AssignmentExpression:
15228 findConstantAndWatchExpressions(ast.left, $filter);
15229 findConstantAndWatchExpressions(ast.right, $filter);
15230 ast.constant = ast.left.constant && ast.right.constant;
15231 ast.toWatch = [ast];
15232 break;
15233 case AST.ArrayExpression:
15234 allConstants = true;
15235 argsToWatch = [];
15236 forEach(ast.elements, function(expr) {
15237 findConstantAndWatchExpressions(expr, $filter);
15238 allConstants = allConstants && expr.constant;
15239 if (!expr.constant) {
15240 argsToWatch.push.apply(argsToWatch, expr.toWatch);
15241 }
15242 });
15243 ast.constant = allConstants;
15244 ast.toWatch = argsToWatch;
15245 break;
15246 case AST.ObjectExpression:
15247 allConstants = true;
15248 argsToWatch = [];
15249 forEach(ast.properties, function(property) {
15250 findConstantAndWatchExpressions(property.value, $filter);
15251 allConstants = allConstants && property.value.constant && !property.computed;
15252 if (!property.value.constant) {
15253 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
15254 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070015255 if (property.computed) {
15256 findConstantAndWatchExpressions(property.key, $filter);
15257 if (!property.key.constant) {
15258 argsToWatch.push.apply(argsToWatch, property.key.toWatch);
15259 }
15260 }
15261
Ed Tanous904063f2017-03-02 16:48:24 -080015262 });
15263 ast.constant = allConstants;
15264 ast.toWatch = argsToWatch;
15265 break;
15266 case AST.ThisExpression:
15267 ast.constant = false;
15268 ast.toWatch = [];
15269 break;
15270 case AST.LocalsExpression:
15271 ast.constant = false;
15272 ast.toWatch = [];
15273 break;
15274 }
15275}
15276
15277function getInputs(body) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015278 if (body.length !== 1) return;
Ed Tanous904063f2017-03-02 16:48:24 -080015279 var lastExpression = body[0].expression;
15280 var candidate = lastExpression.toWatch;
15281 if (candidate.length !== 1) return candidate;
15282 return candidate[0] !== lastExpression ? candidate : undefined;
15283}
15284
15285function isAssignable(ast) {
15286 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
15287}
15288
15289function assignableAST(ast) {
15290 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
15291 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
15292 }
15293}
15294
15295function isLiteral(ast) {
15296 return ast.body.length === 0 ||
15297 ast.body.length === 1 && (
15298 ast.body[0].expression.type === AST.Literal ||
15299 ast.body[0].expression.type === AST.ArrayExpression ||
15300 ast.body[0].expression.type === AST.ObjectExpression);
15301}
15302
15303function isConstant(ast) {
15304 return ast.constant;
15305}
15306
Ed Tanous4758d5b2017-06-06 15:28:13 -070015307function ASTCompiler($filter) {
Ed Tanous904063f2017-03-02 16:48:24 -080015308 this.$filter = $filter;
15309}
15310
15311ASTCompiler.prototype = {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015312 compile: function(ast) {
Ed Tanous904063f2017-03-02 16:48:24 -080015313 var self = this;
Ed Tanous904063f2017-03-02 16:48:24 -080015314 this.state = {
15315 nextId: 0,
15316 filters: {},
Ed Tanous904063f2017-03-02 16:48:24 -080015317 fn: {vars: [], body: [], own: {}},
15318 assign: {vars: [], body: [], own: {}},
15319 inputs: []
15320 };
15321 findConstantAndWatchExpressions(ast, self.$filter);
15322 var extra = '';
15323 var assignable;
15324 this.stage = 'assign';
15325 if ((assignable = assignableAST(ast))) {
15326 this.state.computing = 'assign';
15327 var result = this.nextId();
15328 this.recurse(assignable, result);
15329 this.return_(result);
15330 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
15331 }
15332 var toWatch = getInputs(ast.body);
15333 self.stage = 'inputs';
15334 forEach(toWatch, function(watch, key) {
15335 var fnKey = 'fn' + key;
15336 self.state[fnKey] = {vars: [], body: [], own: {}};
15337 self.state.computing = fnKey;
15338 var intoId = self.nextId();
15339 self.recurse(watch, intoId);
15340 self.return_(intoId);
15341 self.state.inputs.push(fnKey);
15342 watch.watchId = key;
15343 });
15344 this.state.computing = 'fn';
15345 this.stage = 'main';
15346 this.recurse(ast);
15347 var fnString =
15348 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
15349 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
15350 '"' + this.USE + ' ' + this.STRICT + '";\n' +
15351 this.filterPrefix() +
15352 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
15353 extra +
15354 this.watchFns() +
15355 'return fn;';
15356
Ed Tanous4758d5b2017-06-06 15:28:13 -070015357 // eslint-disable-next-line no-new-func
Ed Tanous904063f2017-03-02 16:48:24 -080015358 var fn = (new Function('$filter',
Ed Tanous904063f2017-03-02 16:48:24 -080015359 'getStringValue',
Ed Tanous904063f2017-03-02 16:48:24 -080015360 'ifDefined',
15361 'plus',
Ed Tanous904063f2017-03-02 16:48:24 -080015362 fnString))(
15363 this.$filter,
Ed Tanous904063f2017-03-02 16:48:24 -080015364 getStringValue,
Ed Tanous904063f2017-03-02 16:48:24 -080015365 ifDefined,
Ed Tanous4758d5b2017-06-06 15:28:13 -070015366 plusFn);
Ed Tanous904063f2017-03-02 16:48:24 -080015367 this.state = this.stage = undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080015368 return fn;
15369 },
15370
15371 USE: 'use',
15372
15373 STRICT: 'strict',
15374
15375 watchFns: function() {
15376 var result = [];
15377 var fns = this.state.inputs;
15378 var self = this;
15379 forEach(fns, function(name) {
15380 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
15381 });
15382 if (fns.length) {
15383 result.push('fn.inputs=[' + fns.join(',') + '];');
15384 }
15385 return result.join('');
15386 },
15387
15388 generateFunction: function(name, params) {
15389 return 'function(' + params + '){' +
15390 this.varsPrefix(name) +
15391 this.body(name) +
15392 '};';
15393 },
15394
15395 filterPrefix: function() {
15396 var parts = [];
15397 var self = this;
15398 forEach(this.state.filters, function(id, filter) {
15399 parts.push(id + '=$filter(' + self.escape(filter) + ')');
15400 });
15401 if (parts.length) return 'var ' + parts.join(',') + ';';
15402 return '';
15403 },
15404
15405 varsPrefix: function(section) {
15406 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
15407 },
15408
15409 body: function(section) {
15410 return this.state[section].body.join('');
15411 },
15412
15413 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
15414 var left, right, self = this, args, expression, computed;
15415 recursionFn = recursionFn || noop;
15416 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
15417 intoId = intoId || this.nextId();
15418 this.if_('i',
15419 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
15420 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
15421 );
15422 return;
15423 }
15424 switch (ast.type) {
15425 case AST.Program:
15426 forEach(ast.body, function(expression, pos) {
15427 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
15428 if (pos !== ast.body.length - 1) {
15429 self.current().body.push(right, ';');
15430 } else {
15431 self.return_(right);
15432 }
15433 });
15434 break;
15435 case AST.Literal:
15436 expression = this.escape(ast.value);
15437 this.assign(intoId, expression);
Ed Tanous4758d5b2017-06-06 15:28:13 -070015438 recursionFn(intoId || expression);
Ed Tanous904063f2017-03-02 16:48:24 -080015439 break;
15440 case AST.UnaryExpression:
15441 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
15442 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
15443 this.assign(intoId, expression);
15444 recursionFn(expression);
15445 break;
15446 case AST.BinaryExpression:
15447 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
15448 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
15449 if (ast.operator === '+') {
15450 expression = this.plus(left, right);
15451 } else if (ast.operator === '-') {
15452 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
15453 } else {
15454 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
15455 }
15456 this.assign(intoId, expression);
15457 recursionFn(expression);
15458 break;
15459 case AST.LogicalExpression:
15460 intoId = intoId || this.nextId();
15461 self.recurse(ast.left, intoId);
15462 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
15463 recursionFn(intoId);
15464 break;
15465 case AST.ConditionalExpression:
15466 intoId = intoId || this.nextId();
15467 self.recurse(ast.test, intoId);
15468 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
15469 recursionFn(intoId);
15470 break;
15471 case AST.Identifier:
15472 intoId = intoId || this.nextId();
15473 if (nameId) {
15474 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
15475 nameId.computed = false;
15476 nameId.name = ast.name;
15477 }
Ed Tanous904063f2017-03-02 16:48:24 -080015478 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
15479 function() {
15480 self.if_(self.stage === 'inputs' || 's', function() {
15481 if (create && create !== 1) {
15482 self.if_(
Ed Tanous4758d5b2017-06-06 15:28:13 -070015483 self.isNull(self.nonComputedMember('s', ast.name)),
Ed Tanous904063f2017-03-02 16:48:24 -080015484 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
15485 }
15486 self.assign(intoId, self.nonComputedMember('s', ast.name));
15487 });
15488 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
15489 );
Ed Tanous904063f2017-03-02 16:48:24 -080015490 recursionFn(intoId);
15491 break;
15492 case AST.MemberExpression:
15493 left = nameId && (nameId.context = this.nextId()) || this.nextId();
15494 intoId = intoId || this.nextId();
15495 self.recurse(ast.object, left, undefined, function() {
15496 self.if_(self.notNull(left), function() {
Ed Tanous904063f2017-03-02 16:48:24 -080015497 if (ast.computed) {
15498 right = self.nextId();
15499 self.recurse(ast.property, right);
15500 self.getStringValue(right);
Ed Tanous904063f2017-03-02 16:48:24 -080015501 if (create && create !== 1) {
15502 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
15503 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070015504 expression = self.computedMember(left, right);
Ed Tanous904063f2017-03-02 16:48:24 -080015505 self.assign(intoId, expression);
15506 if (nameId) {
15507 nameId.computed = true;
15508 nameId.name = right;
15509 }
15510 } else {
Ed Tanous904063f2017-03-02 16:48:24 -080015511 if (create && create !== 1) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015512 self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
Ed Tanous904063f2017-03-02 16:48:24 -080015513 }
15514 expression = self.nonComputedMember(left, ast.property.name);
Ed Tanous904063f2017-03-02 16:48:24 -080015515 self.assign(intoId, expression);
15516 if (nameId) {
15517 nameId.computed = false;
15518 nameId.name = ast.property.name;
15519 }
15520 }
15521 }, function() {
15522 self.assign(intoId, 'undefined');
15523 });
15524 recursionFn(intoId);
15525 }, !!create);
15526 break;
15527 case AST.CallExpression:
15528 intoId = intoId || this.nextId();
15529 if (ast.filter) {
15530 right = self.filter(ast.callee.name);
15531 args = [];
15532 forEach(ast.arguments, function(expr) {
15533 var argument = self.nextId();
15534 self.recurse(expr, argument);
15535 args.push(argument);
15536 });
15537 expression = right + '(' + args.join(',') + ')';
15538 self.assign(intoId, expression);
15539 recursionFn(intoId);
15540 } else {
15541 right = self.nextId();
15542 left = {};
15543 args = [];
15544 self.recurse(ast.callee, right, left, function() {
15545 self.if_(self.notNull(right), function() {
Ed Tanous904063f2017-03-02 16:48:24 -080015546 forEach(ast.arguments, function(expr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015547 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
15548 args.push(argument);
Ed Tanous904063f2017-03-02 16:48:24 -080015549 });
15550 });
15551 if (left.name) {
Ed Tanous904063f2017-03-02 16:48:24 -080015552 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
15553 } else {
15554 expression = right + '(' + args.join(',') + ')';
15555 }
Ed Tanous904063f2017-03-02 16:48:24 -080015556 self.assign(intoId, expression);
15557 }, function() {
15558 self.assign(intoId, 'undefined');
15559 });
15560 recursionFn(intoId);
15561 });
15562 }
15563 break;
15564 case AST.AssignmentExpression:
15565 right = this.nextId();
15566 left = {};
Ed Tanous904063f2017-03-02 16:48:24 -080015567 this.recurse(ast.left, undefined, left, function() {
15568 self.if_(self.notNull(left.context), function() {
15569 self.recurse(ast.right, right);
Ed Tanous904063f2017-03-02 16:48:24 -080015570 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
15571 self.assign(intoId, expression);
15572 recursionFn(intoId || expression);
15573 });
15574 }, 1);
15575 break;
15576 case AST.ArrayExpression:
15577 args = [];
15578 forEach(ast.elements, function(expr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015579 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
Ed Tanous904063f2017-03-02 16:48:24 -080015580 args.push(argument);
15581 });
15582 });
15583 expression = '[' + args.join(',') + ']';
15584 this.assign(intoId, expression);
Ed Tanous4758d5b2017-06-06 15:28:13 -070015585 recursionFn(intoId || expression);
Ed Tanous904063f2017-03-02 16:48:24 -080015586 break;
15587 case AST.ObjectExpression:
15588 args = [];
15589 computed = false;
15590 forEach(ast.properties, function(property) {
15591 if (property.computed) {
15592 computed = true;
15593 }
15594 });
15595 if (computed) {
15596 intoId = intoId || this.nextId();
15597 this.assign(intoId, '{}');
15598 forEach(ast.properties, function(property) {
15599 if (property.computed) {
15600 left = self.nextId();
15601 self.recurse(property.key, left);
15602 } else {
15603 left = property.key.type === AST.Identifier ?
15604 property.key.name :
15605 ('' + property.key.value);
15606 }
15607 right = self.nextId();
15608 self.recurse(property.value, right);
15609 self.assign(self.member(intoId, left, property.computed), right);
15610 });
15611 } else {
15612 forEach(ast.properties, function(property) {
15613 self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) {
15614 args.push(self.escape(
15615 property.key.type === AST.Identifier ? property.key.name :
15616 ('' + property.key.value)) +
15617 ':' + expr);
15618 });
15619 });
15620 expression = '{' + args.join(',') + '}';
15621 this.assign(intoId, expression);
15622 }
15623 recursionFn(intoId || expression);
15624 break;
15625 case AST.ThisExpression:
15626 this.assign(intoId, 's');
Ed Tanous4758d5b2017-06-06 15:28:13 -070015627 recursionFn(intoId || 's');
Ed Tanous904063f2017-03-02 16:48:24 -080015628 break;
15629 case AST.LocalsExpression:
15630 this.assign(intoId, 'l');
Ed Tanous4758d5b2017-06-06 15:28:13 -070015631 recursionFn(intoId || 'l');
Ed Tanous904063f2017-03-02 16:48:24 -080015632 break;
15633 case AST.NGValueParameter:
15634 this.assign(intoId, 'v');
Ed Tanous4758d5b2017-06-06 15:28:13 -070015635 recursionFn(intoId || 'v');
Ed Tanous904063f2017-03-02 16:48:24 -080015636 break;
15637 }
15638 },
15639
15640 getHasOwnProperty: function(element, property) {
15641 var key = element + '.' + property;
15642 var own = this.current().own;
15643 if (!own.hasOwnProperty(key)) {
15644 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
15645 }
15646 return own[key];
15647 },
15648
15649 assign: function(id, value) {
15650 if (!id) return;
15651 this.current().body.push(id, '=', value, ';');
15652 return id;
15653 },
15654
15655 filter: function(filterName) {
15656 if (!this.state.filters.hasOwnProperty(filterName)) {
15657 this.state.filters[filterName] = this.nextId(true);
15658 }
15659 return this.state.filters[filterName];
15660 },
15661
15662 ifDefined: function(id, defaultValue) {
15663 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
15664 },
15665
15666 plus: function(left, right) {
15667 return 'plus(' + left + ',' + right + ')';
15668 },
15669
15670 return_: function(id) {
15671 this.current().body.push('return ', id, ';');
15672 },
15673
15674 if_: function(test, alternate, consequent) {
15675 if (test === true) {
15676 alternate();
15677 } else {
15678 var body = this.current().body;
15679 body.push('if(', test, '){');
15680 alternate();
15681 body.push('}');
15682 if (consequent) {
15683 body.push('else{');
15684 consequent();
15685 body.push('}');
15686 }
15687 }
15688 },
15689
15690 not: function(expression) {
15691 return '!(' + expression + ')';
15692 },
15693
Ed Tanous4758d5b2017-06-06 15:28:13 -070015694 isNull: function(expression) {
15695 return expression + '==null';
15696 },
15697
Ed Tanous904063f2017-03-02 16:48:24 -080015698 notNull: function(expression) {
15699 return expression + '!=null';
15700 },
15701
15702 nonComputedMember: function(left, right) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015703 var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
Ed Tanous904063f2017-03-02 16:48:24 -080015704 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
15705 if (SAFE_IDENTIFIER.test(right)) {
15706 return left + '.' + right;
15707 } else {
15708 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]';
15709 }
15710 },
15711
15712 computedMember: function(left, right) {
15713 return left + '[' + right + ']';
15714 },
15715
15716 member: function(left, right, computed) {
15717 if (computed) return this.computedMember(left, right);
15718 return this.nonComputedMember(left, right);
15719 },
15720
Ed Tanous904063f2017-03-02 16:48:24 -080015721 getStringValue: function(item) {
15722 this.assign(item, 'getStringValue(' + item + ')');
15723 },
15724
Ed Tanous904063f2017-03-02 16:48:24 -080015725 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
15726 var self = this;
15727 return function() {
15728 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
15729 };
15730 },
15731
15732 lazyAssign: function(id, value) {
15733 var self = this;
15734 return function() {
15735 self.assign(id, value);
15736 };
15737 },
15738
15739 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
15740
15741 stringEscapeFn: function(c) {
15742 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
15743 },
15744
15745 escape: function(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015746 if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\'';
Ed Tanous904063f2017-03-02 16:48:24 -080015747 if (isNumber(value)) return value.toString();
15748 if (value === true) return 'true';
15749 if (value === false) return 'false';
15750 if (value === null) return 'null';
15751 if (typeof value === 'undefined') return 'undefined';
15752
15753 throw $parseMinErr('esc', 'IMPOSSIBLE');
15754 },
15755
15756 nextId: function(skip, init) {
15757 var id = 'v' + (this.state.nextId++);
15758 if (!skip) {
15759 this.current().vars.push(id + (init ? '=' + init : ''));
15760 }
15761 return id;
15762 },
15763
15764 current: function() {
15765 return this.state[this.state.computing];
15766 }
15767};
15768
15769
Ed Tanous4758d5b2017-06-06 15:28:13 -070015770function ASTInterpreter($filter) {
Ed Tanous904063f2017-03-02 16:48:24 -080015771 this.$filter = $filter;
15772}
15773
15774ASTInterpreter.prototype = {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015775 compile: function(ast) {
Ed Tanous904063f2017-03-02 16:48:24 -080015776 var self = this;
Ed Tanous904063f2017-03-02 16:48:24 -080015777 findConstantAndWatchExpressions(ast, self.$filter);
15778 var assignable;
15779 var assign;
15780 if ((assignable = assignableAST(ast))) {
15781 assign = this.recurse(assignable);
15782 }
15783 var toWatch = getInputs(ast.body);
15784 var inputs;
15785 if (toWatch) {
15786 inputs = [];
15787 forEach(toWatch, function(watch, key) {
15788 var input = self.recurse(watch);
15789 watch.input = input;
15790 inputs.push(input);
15791 watch.watchId = key;
15792 });
15793 }
15794 var expressions = [];
15795 forEach(ast.body, function(expression) {
15796 expressions.push(self.recurse(expression.expression));
15797 });
15798 var fn = ast.body.length === 0 ? noop :
15799 ast.body.length === 1 ? expressions[0] :
15800 function(scope, locals) {
15801 var lastValue;
15802 forEach(expressions, function(exp) {
15803 lastValue = exp(scope, locals);
15804 });
15805 return lastValue;
15806 };
15807 if (assign) {
15808 fn.assign = function(scope, value, locals) {
15809 return assign(scope, locals, value);
15810 };
15811 }
15812 if (inputs) {
15813 fn.inputs = inputs;
15814 }
Ed Tanous904063f2017-03-02 16:48:24 -080015815 return fn;
15816 },
15817
15818 recurse: function(ast, context, create) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015819 var left, right, self = this, args;
Ed Tanous904063f2017-03-02 16:48:24 -080015820 if (ast.input) {
15821 return this.inputs(ast.input, ast.watchId);
15822 }
15823 switch (ast.type) {
15824 case AST.Literal:
15825 return this.value(ast.value, context);
15826 case AST.UnaryExpression:
15827 right = this.recurse(ast.argument);
15828 return this['unary' + ast.operator](right, context);
15829 case AST.BinaryExpression:
15830 left = this.recurse(ast.left);
15831 right = this.recurse(ast.right);
15832 return this['binary' + ast.operator](left, right, context);
15833 case AST.LogicalExpression:
15834 left = this.recurse(ast.left);
15835 right = this.recurse(ast.right);
15836 return this['binary' + ast.operator](left, right, context);
15837 case AST.ConditionalExpression:
15838 return this['ternary?:'](
15839 this.recurse(ast.test),
15840 this.recurse(ast.alternate),
15841 this.recurse(ast.consequent),
15842 context
15843 );
15844 case AST.Identifier:
Ed Tanous4758d5b2017-06-06 15:28:13 -070015845 return self.identifier(ast.name, context, create);
Ed Tanous904063f2017-03-02 16:48:24 -080015846 case AST.MemberExpression:
15847 left = this.recurse(ast.object, false, !!create);
15848 if (!ast.computed) {
Ed Tanous904063f2017-03-02 16:48:24 -080015849 right = ast.property.name;
15850 }
15851 if (ast.computed) right = this.recurse(ast.property);
15852 return ast.computed ?
Ed Tanous4758d5b2017-06-06 15:28:13 -070015853 this.computedMember(left, right, context, create) :
15854 this.nonComputedMember(left, right, context, create);
Ed Tanous904063f2017-03-02 16:48:24 -080015855 case AST.CallExpression:
15856 args = [];
15857 forEach(ast.arguments, function(expr) {
15858 args.push(self.recurse(expr));
15859 });
15860 if (ast.filter) right = this.$filter(ast.callee.name);
15861 if (!ast.filter) right = this.recurse(ast.callee, true);
15862 return ast.filter ?
15863 function(scope, locals, assign, inputs) {
15864 var values = [];
15865 for (var i = 0; i < args.length; ++i) {
15866 values.push(args[i](scope, locals, assign, inputs));
15867 }
15868 var value = right.apply(undefined, values, inputs);
15869 return context ? {context: undefined, name: undefined, value: value} : value;
15870 } :
15871 function(scope, locals, assign, inputs) {
15872 var rhs = right(scope, locals, assign, inputs);
15873 var value;
15874 if (rhs.value != null) {
Ed Tanous904063f2017-03-02 16:48:24 -080015875 var values = [];
15876 for (var i = 0; i < args.length; ++i) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015877 values.push(args[i](scope, locals, assign, inputs));
Ed Tanous904063f2017-03-02 16:48:24 -080015878 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070015879 value = rhs.value.apply(rhs.context, values);
Ed Tanous904063f2017-03-02 16:48:24 -080015880 }
15881 return context ? {value: value} : value;
15882 };
15883 case AST.AssignmentExpression:
15884 left = this.recurse(ast.left, true, 1);
15885 right = this.recurse(ast.right);
15886 return function(scope, locals, assign, inputs) {
15887 var lhs = left(scope, locals, assign, inputs);
15888 var rhs = right(scope, locals, assign, inputs);
Ed Tanous904063f2017-03-02 16:48:24 -080015889 lhs.context[lhs.name] = rhs;
15890 return context ? {value: rhs} : rhs;
15891 };
15892 case AST.ArrayExpression:
15893 args = [];
15894 forEach(ast.elements, function(expr) {
15895 args.push(self.recurse(expr));
15896 });
15897 return function(scope, locals, assign, inputs) {
15898 var value = [];
15899 for (var i = 0; i < args.length; ++i) {
15900 value.push(args[i](scope, locals, assign, inputs));
15901 }
15902 return context ? {value: value} : value;
15903 };
15904 case AST.ObjectExpression:
15905 args = [];
15906 forEach(ast.properties, function(property) {
15907 if (property.computed) {
15908 args.push({key: self.recurse(property.key),
15909 computed: true,
15910 value: self.recurse(property.value)
15911 });
15912 } else {
15913 args.push({key: property.key.type === AST.Identifier ?
15914 property.key.name :
15915 ('' + property.key.value),
15916 computed: false,
15917 value: self.recurse(property.value)
15918 });
15919 }
15920 });
15921 return function(scope, locals, assign, inputs) {
15922 var value = {};
15923 for (var i = 0; i < args.length; ++i) {
15924 if (args[i].computed) {
15925 value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs);
15926 } else {
15927 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
15928 }
15929 }
15930 return context ? {value: value} : value;
15931 };
15932 case AST.ThisExpression:
15933 return function(scope) {
15934 return context ? {value: scope} : scope;
15935 };
15936 case AST.LocalsExpression:
15937 return function(scope, locals) {
15938 return context ? {value: locals} : locals;
15939 };
15940 case AST.NGValueParameter:
15941 return function(scope, locals, assign) {
15942 return context ? {value: assign} : assign;
15943 };
15944 }
15945 },
15946
15947 'unary+': function(argument, context) {
15948 return function(scope, locals, assign, inputs) {
15949 var arg = argument(scope, locals, assign, inputs);
15950 if (isDefined(arg)) {
15951 arg = +arg;
15952 } else {
15953 arg = 0;
15954 }
15955 return context ? {value: arg} : arg;
15956 };
15957 },
15958 'unary-': function(argument, context) {
15959 return function(scope, locals, assign, inputs) {
15960 var arg = argument(scope, locals, assign, inputs);
15961 if (isDefined(arg)) {
15962 arg = -arg;
15963 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070015964 arg = -0;
Ed Tanous904063f2017-03-02 16:48:24 -080015965 }
15966 return context ? {value: arg} : arg;
15967 };
15968 },
15969 'unary!': function(argument, context) {
15970 return function(scope, locals, assign, inputs) {
15971 var arg = !argument(scope, locals, assign, inputs);
15972 return context ? {value: arg} : arg;
15973 };
15974 },
15975 'binary+': function(left, right, context) {
15976 return function(scope, locals, assign, inputs) {
15977 var lhs = left(scope, locals, assign, inputs);
15978 var rhs = right(scope, locals, assign, inputs);
15979 var arg = plusFn(lhs, rhs);
15980 return context ? {value: arg} : arg;
15981 };
15982 },
15983 'binary-': function(left, right, context) {
15984 return function(scope, locals, assign, inputs) {
15985 var lhs = left(scope, locals, assign, inputs);
15986 var rhs = right(scope, locals, assign, inputs);
15987 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
15988 return context ? {value: arg} : arg;
15989 };
15990 },
15991 'binary*': function(left, right, context) {
15992 return function(scope, locals, assign, inputs) {
15993 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
15994 return context ? {value: arg} : arg;
15995 };
15996 },
15997 'binary/': function(left, right, context) {
15998 return function(scope, locals, assign, inputs) {
15999 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
16000 return context ? {value: arg} : arg;
16001 };
16002 },
16003 'binary%': function(left, right, context) {
16004 return function(scope, locals, assign, inputs) {
16005 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
16006 return context ? {value: arg} : arg;
16007 };
16008 },
16009 'binary===': function(left, right, context) {
16010 return function(scope, locals, assign, inputs) {
16011 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
16012 return context ? {value: arg} : arg;
16013 };
16014 },
16015 'binary!==': function(left, right, context) {
16016 return function(scope, locals, assign, inputs) {
16017 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
16018 return context ? {value: arg} : arg;
16019 };
16020 },
16021 'binary==': function(left, right, context) {
16022 return function(scope, locals, assign, inputs) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016023 // eslint-disable-next-line eqeqeq
Ed Tanous904063f2017-03-02 16:48:24 -080016024 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
16025 return context ? {value: arg} : arg;
16026 };
16027 },
16028 'binary!=': function(left, right, context) {
16029 return function(scope, locals, assign, inputs) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016030 // eslint-disable-next-line eqeqeq
Ed Tanous904063f2017-03-02 16:48:24 -080016031 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
16032 return context ? {value: arg} : arg;
16033 };
16034 },
16035 'binary<': function(left, right, context) {
16036 return function(scope, locals, assign, inputs) {
16037 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
16038 return context ? {value: arg} : arg;
16039 };
16040 },
16041 'binary>': function(left, right, context) {
16042 return function(scope, locals, assign, inputs) {
16043 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
16044 return context ? {value: arg} : arg;
16045 };
16046 },
16047 'binary<=': function(left, right, context) {
16048 return function(scope, locals, assign, inputs) {
16049 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
16050 return context ? {value: arg} : arg;
16051 };
16052 },
16053 'binary>=': function(left, right, context) {
16054 return function(scope, locals, assign, inputs) {
16055 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
16056 return context ? {value: arg} : arg;
16057 };
16058 },
16059 'binary&&': function(left, right, context) {
16060 return function(scope, locals, assign, inputs) {
16061 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
16062 return context ? {value: arg} : arg;
16063 };
16064 },
16065 'binary||': function(left, right, context) {
16066 return function(scope, locals, assign, inputs) {
16067 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
16068 return context ? {value: arg} : arg;
16069 };
16070 },
16071 'ternary?:': function(test, alternate, consequent, context) {
16072 return function(scope, locals, assign, inputs) {
16073 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
16074 return context ? {value: arg} : arg;
16075 };
16076 },
16077 value: function(value, context) {
16078 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
16079 },
Ed Tanous4758d5b2017-06-06 15:28:13 -070016080 identifier: function(name, context, create) {
Ed Tanous904063f2017-03-02 16:48:24 -080016081 return function(scope, locals, assign, inputs) {
16082 var base = locals && (name in locals) ? locals : scope;
Ed Tanous4758d5b2017-06-06 15:28:13 -070016083 if (create && create !== 1 && base && base[name] == null) {
Ed Tanous904063f2017-03-02 16:48:24 -080016084 base[name] = {};
16085 }
16086 var value = base ? base[name] : undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080016087 if (context) {
16088 return {context: base, name: name, value: value};
16089 } else {
16090 return value;
16091 }
16092 };
16093 },
Ed Tanous4758d5b2017-06-06 15:28:13 -070016094 computedMember: function(left, right, context, create) {
Ed Tanous904063f2017-03-02 16:48:24 -080016095 return function(scope, locals, assign, inputs) {
16096 var lhs = left(scope, locals, assign, inputs);
16097 var rhs;
16098 var value;
16099 if (lhs != null) {
16100 rhs = right(scope, locals, assign, inputs);
16101 rhs = getStringValue(rhs);
Ed Tanous904063f2017-03-02 16:48:24 -080016102 if (create && create !== 1) {
Ed Tanous904063f2017-03-02 16:48:24 -080016103 if (lhs && !(lhs[rhs])) {
16104 lhs[rhs] = {};
16105 }
16106 }
16107 value = lhs[rhs];
Ed Tanous904063f2017-03-02 16:48:24 -080016108 }
16109 if (context) {
16110 return {context: lhs, name: rhs, value: value};
16111 } else {
16112 return value;
16113 }
16114 };
16115 },
Ed Tanous4758d5b2017-06-06 15:28:13 -070016116 nonComputedMember: function(left, right, context, create) {
Ed Tanous904063f2017-03-02 16:48:24 -080016117 return function(scope, locals, assign, inputs) {
16118 var lhs = left(scope, locals, assign, inputs);
16119 if (create && create !== 1) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016120 if (lhs && lhs[right] == null) {
Ed Tanous904063f2017-03-02 16:48:24 -080016121 lhs[right] = {};
16122 }
16123 }
16124 var value = lhs != null ? lhs[right] : undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080016125 if (context) {
16126 return {context: lhs, name: right, value: value};
16127 } else {
16128 return value;
16129 }
16130 };
16131 },
16132 inputs: function(input, watchId) {
16133 return function(scope, value, locals, inputs) {
16134 if (inputs) return inputs[watchId];
16135 return input(scope, value, locals);
16136 };
16137 }
16138};
16139
16140/**
16141 * @constructor
16142 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070016143function Parser(lexer, $filter, options) {
Ed Tanous904063f2017-03-02 16:48:24 -080016144 this.ast = new AST(lexer, options);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016145 this.astCompiler = options.csp ? new ASTInterpreter($filter) :
16146 new ASTCompiler($filter);
16147}
Ed Tanous904063f2017-03-02 16:48:24 -080016148
16149Parser.prototype = {
16150 constructor: Parser,
16151
16152 parse: function(text) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016153 var ast = this.ast.ast(text);
16154 var fn = this.astCompiler.compile(ast);
16155 fn.literal = isLiteral(ast);
16156 fn.constant = isConstant(ast);
16157 return fn;
Ed Tanous904063f2017-03-02 16:48:24 -080016158 }
16159};
16160
Ed Tanous904063f2017-03-02 16:48:24 -080016161function getValueOf(value) {
16162 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
16163}
16164
16165///////////////////////////////////
16166
16167/**
16168 * @ngdoc service
16169 * @name $parse
16170 * @kind function
16171 *
16172 * @description
16173 *
16174 * Converts Angular {@link guide/expression expression} into a function.
16175 *
16176 * ```js
16177 * var getter = $parse('user.name');
16178 * var setter = getter.assign;
16179 * var context = {user:{name:'angular'}};
16180 * var locals = {user:{name:'local'}};
16181 *
16182 * expect(getter(context)).toEqual('angular');
16183 * setter(context, 'newValue');
16184 * expect(context.user.name).toEqual('newValue');
16185 * expect(getter(context, locals)).toEqual('local');
16186 * ```
16187 *
16188 *
16189 * @param {string} expression String expression to compile.
16190 * @returns {function(context, locals)} a function which represents the compiled expression:
16191 *
16192 * * `context` – `{object}` – an object against which any expressions embedded in the strings
16193 * are evaluated against (typically a scope object).
16194 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
16195 * `context`.
16196 *
16197 * The returned function also has the following properties:
16198 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
16199 * literal.
16200 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
16201 * constant literals.
16202 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
16203 * set to a function to change its value on the given context.
16204 *
16205 */
16206
16207
16208/**
16209 * @ngdoc provider
16210 * @name $parseProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070016211 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080016212 *
16213 * @description
16214 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
16215 * service.
16216 */
16217function $ParseProvider() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016218 var cache = createMap();
Ed Tanous904063f2017-03-02 16:48:24 -080016219 var literals = {
16220 'true': true,
16221 'false': false,
16222 'null': null,
16223 'undefined': undefined
16224 };
16225 var identStart, identContinue;
16226
16227 /**
16228 * @ngdoc method
16229 * @name $parseProvider#addLiteral
16230 * @description
16231 *
16232 * Configure $parse service to add literal values that will be present as literal at expressions.
16233 *
16234 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name.
16235 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`.
16236 *
16237 **/
16238 this.addLiteral = function(literalName, literalValue) {
16239 literals[literalName] = literalValue;
16240 };
16241
16242 /**
16243 * @ngdoc method
16244 * @name $parseProvider#setIdentifierFns
Ed Tanous4758d5b2017-06-06 15:28:13 -070016245 *
Ed Tanous904063f2017-03-02 16:48:24 -080016246 * @description
16247 *
16248 * Allows defining the set of characters that are allowed in Angular expressions. The function
16249 * `identifierStart` will get called to know if a given character is a valid character to be the
16250 * first character for an identifier. The function `identifierContinue` will get called to know if
16251 * a given character is a valid character to be a follow-up identifier character. The functions
16252 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
16253 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
16254 * mind that the `string` parameter can be two characters long depending on the character
16255 * representation. It is expected for the function to return `true` or `false`, whether that
16256 * character is allowed or not.
16257 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070016258 * Since this function will be called extensively, keep the implementation of these functions fast,
Ed Tanous904063f2017-03-02 16:48:24 -080016259 * as the performance of these functions have a direct impact on the expressions parsing speed.
16260 *
16261 * @param {function=} identifierStart The function that will decide whether the given character is
16262 * a valid identifier start character.
16263 * @param {function=} identifierContinue The function that will decide whether the given character is
16264 * a valid identifier continue character.
16265 */
16266 this.setIdentifierFns = function(identifierStart, identifierContinue) {
16267 identStart = identifierStart;
16268 identContinue = identifierContinue;
16269 return this;
16270 };
16271
16272 this.$get = ['$filter', function($filter) {
16273 var noUnsafeEval = csp().noUnsafeEval;
16274 var $parseOptions = {
16275 csp: noUnsafeEval,
Ed Tanous904063f2017-03-02 16:48:24 -080016276 literals: copy(literals),
16277 isIdentifierStart: isFunction(identStart) && identStart,
16278 isIdentifierContinue: isFunction(identContinue) && identContinue
16279 };
Ed Tanous904063f2017-03-02 16:48:24 -080016280 return $parse;
16281
Ed Tanous4758d5b2017-06-06 15:28:13 -070016282 function $parse(exp, interceptorFn) {
Ed Tanous904063f2017-03-02 16:48:24 -080016283 var parsedExpression, oneTime, cacheKey;
16284
Ed Tanous904063f2017-03-02 16:48:24 -080016285 switch (typeof exp) {
16286 case 'string':
16287 exp = exp.trim();
16288 cacheKey = exp;
16289
Ed Tanous904063f2017-03-02 16:48:24 -080016290 parsedExpression = cache[cacheKey];
16291
16292 if (!parsedExpression) {
16293 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
16294 oneTime = true;
16295 exp = exp.substring(2);
16296 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070016297 var lexer = new Lexer($parseOptions);
16298 var parser = new Parser(lexer, $filter, $parseOptions);
Ed Tanous904063f2017-03-02 16:48:24 -080016299 parsedExpression = parser.parse(exp);
16300 if (parsedExpression.constant) {
16301 parsedExpression.$$watchDelegate = constantWatchDelegate;
16302 } else if (oneTime) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016303 parsedExpression.oneTime = true;
16304 parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
Ed Tanous904063f2017-03-02 16:48:24 -080016305 } else if (parsedExpression.inputs) {
16306 parsedExpression.$$watchDelegate = inputsWatchDelegate;
16307 }
Ed Tanous904063f2017-03-02 16:48:24 -080016308 cache[cacheKey] = parsedExpression;
16309 }
16310 return addInterceptor(parsedExpression, interceptorFn);
16311
16312 case 'function':
16313 return addInterceptor(exp, interceptorFn);
16314
16315 default:
16316 return addInterceptor(noop, interceptorFn);
16317 }
16318 }
16319
Ed Tanous4758d5b2017-06-06 15:28:13 -070016320 function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
Ed Tanous904063f2017-03-02 16:48:24 -080016321
16322 if (newValue == null || oldValueOfValue == null) { // null/undefined
16323 return newValue === oldValueOfValue;
16324 }
16325
16326 if (typeof newValue === 'object') {
16327
16328 // attempt to convert the value to a primitive type
16329 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
16330 // be cheaply dirty-checked
16331 newValue = getValueOf(newValue);
16332
Ed Tanous4758d5b2017-06-06 15:28:13 -070016333 if (typeof newValue === 'object' && !compareObjectIdentity) {
Ed Tanous904063f2017-03-02 16:48:24 -080016334 // objects/arrays are not supported - deep-watching them would be too expensive
16335 return false;
16336 }
16337
16338 // fall-through to the primitive equality check
16339 }
16340
16341 //Primitive or NaN
Ed Tanous4758d5b2017-06-06 15:28:13 -070016342 // eslint-disable-next-line no-self-compare
Ed Tanous904063f2017-03-02 16:48:24 -080016343 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
16344 }
16345
16346 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
16347 var inputExpressions = parsedExpression.inputs;
16348 var lastResult;
16349
16350 if (inputExpressions.length === 1) {
16351 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
16352 inputExpressions = inputExpressions[0];
16353 return scope.$watch(function expressionInputWatch(scope) {
16354 var newInputValue = inputExpressions(scope);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016355 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, parsedExpression.literal)) {
Ed Tanous904063f2017-03-02 16:48:24 -080016356 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
16357 oldInputValueOf = newInputValue && getValueOf(newInputValue);
16358 }
16359 return lastResult;
16360 }, listener, objectEquality, prettyPrintExpression);
16361 }
16362
16363 var oldInputValueOfValues = [];
16364 var oldInputValues = [];
16365 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
16366 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
16367 oldInputValues[i] = null;
16368 }
16369
16370 return scope.$watch(function expressionInputsWatch(scope) {
16371 var changed = false;
16372
16373 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
16374 var newInputValue = inputExpressions[i](scope);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016375 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], parsedExpression.literal))) {
Ed Tanous904063f2017-03-02 16:48:24 -080016376 oldInputValues[i] = newInputValue;
16377 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
16378 }
16379 }
16380
16381 if (changed) {
16382 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
16383 }
16384
16385 return lastResult;
16386 }, listener, objectEquality, prettyPrintExpression);
16387 }
16388
Ed Tanous4758d5b2017-06-06 15:28:13 -070016389 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
16390 var isDone = parsedExpression.literal ? isAllDefined : isDefined;
Ed Tanous904063f2017-03-02 16:48:24 -080016391 var unwatch, lastValue;
Ed Tanous4758d5b2017-06-06 15:28:13 -070016392 if (parsedExpression.inputs) {
16393 unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression);
16394 } else {
16395 unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality);
16396 }
16397 return unwatch;
16398
16399 function oneTimeWatch(scope) {
Ed Tanous904063f2017-03-02 16:48:24 -080016400 return parsedExpression(scope);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016401 }
16402 function oneTimeListener(value, old, scope) {
Ed Tanous904063f2017-03-02 16:48:24 -080016403 lastValue = value;
16404 if (isFunction(listener)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016405 listener(value, old, scope);
Ed Tanous904063f2017-03-02 16:48:24 -080016406 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070016407 if (isDone(value)) {
Ed Tanous904063f2017-03-02 16:48:24 -080016408 scope.$$postDigest(function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016409 if (isDone(lastValue)) {
Ed Tanous904063f2017-03-02 16:48:24 -080016410 unwatch();
16411 }
16412 });
16413 }
Ed Tanous904063f2017-03-02 16:48:24 -080016414 }
16415 }
16416
Ed Tanous4758d5b2017-06-06 15:28:13 -070016417 function isAllDefined(value) {
16418 var allDefined = true;
16419 forEach(value, function(val) {
16420 if (!isDefined(val)) allDefined = false;
16421 });
16422 return allDefined;
16423 }
16424
Ed Tanous904063f2017-03-02 16:48:24 -080016425 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016426 var unwatch = scope.$watch(function constantWatch(scope) {
Ed Tanous904063f2017-03-02 16:48:24 -080016427 unwatch();
16428 return parsedExpression(scope);
16429 }, listener, objectEquality);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016430 return unwatch;
Ed Tanous904063f2017-03-02 16:48:24 -080016431 }
16432
16433 function addInterceptor(parsedExpression, interceptorFn) {
16434 if (!interceptorFn) return parsedExpression;
16435 var watchDelegate = parsedExpression.$$watchDelegate;
16436 var useInputs = false;
16437
Ed Tanous4758d5b2017-06-06 15:28:13 -070016438 var isDone = parsedExpression.literal ? isAllDefined : isDefined;
Ed Tanous904063f2017-03-02 16:48:24 -080016439
Ed Tanous4758d5b2017-06-06 15:28:13 -070016440 function regularInterceptedExpression(scope, locals, assign, inputs) {
Ed Tanous904063f2017-03-02 16:48:24 -080016441 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
16442 return interceptorFn(value, scope, locals);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016443 }
16444
16445 function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
16446 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
Ed Tanous904063f2017-03-02 16:48:24 -080016447 var result = interceptorFn(value, scope, locals);
16448 // we only return the interceptor's result if the
16449 // initial value is defined (for bind-once)
Ed Tanous4758d5b2017-06-06 15:28:13 -070016450 return isDone(value) ? result : value;
16451 }
Ed Tanous904063f2017-03-02 16:48:24 -080016452
Ed Tanous4758d5b2017-06-06 15:28:13 -070016453 var fn = parsedExpression.oneTime ? oneTimeInterceptedExpression : regularInterceptedExpression;
16454
16455 // Propogate the literal/oneTime attributes
16456 fn.literal = parsedExpression.literal;
16457 fn.oneTime = parsedExpression.oneTime;
16458
16459 // Propagate or create inputs / $$watchDelegates
16460 useInputs = !parsedExpression.inputs;
16461 if (watchDelegate && watchDelegate !== inputsWatchDelegate) {
16462 fn.$$watchDelegate = watchDelegate;
16463 fn.inputs = parsedExpression.inputs;
Ed Tanous904063f2017-03-02 16:48:24 -080016464 } else if (!interceptorFn.$stateful) {
16465 // If there is an interceptor, but no watchDelegate then treat the interceptor like
16466 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
16467 fn.$$watchDelegate = inputsWatchDelegate;
Ed Tanous904063f2017-03-02 16:48:24 -080016468 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
16469 }
16470
16471 return fn;
16472 }
16473 }];
16474}
16475
16476/**
16477 * @ngdoc service
16478 * @name $q
16479 * @requires $rootScope
16480 *
16481 * @description
16482 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
16483 * when they are done processing.
16484 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070016485 * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred
16486 * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
Ed Tanous904063f2017-03-02 16:48:24 -080016487 *
16488 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
16489 * implementations, and the other which resembles ES6 (ES2015) promises to some degree.
16490 *
16491 * # $q constructor
16492 *
16493 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
16494 * function as the first argument. This is similar to the native Promise implementation from ES6,
16495 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
16496 *
16497 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
16498 * available yet.
16499 *
16500 * It can be used like so:
16501 *
16502 * ```js
16503 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
16504 * // are available in the current lexical scope (they could have been injected or passed in).
16505 *
16506 * function asyncGreet(name) {
16507 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
16508 * return $q(function(resolve, reject) {
16509 * setTimeout(function() {
16510 * if (okToGreet(name)) {
16511 * resolve('Hello, ' + name + '!');
16512 * } else {
16513 * reject('Greeting ' + name + ' is not allowed.');
16514 * }
16515 * }, 1000);
16516 * });
16517 * }
16518 *
16519 * var promise = asyncGreet('Robin Hood');
16520 * promise.then(function(greeting) {
16521 * alert('Success: ' + greeting);
16522 * }, function(reason) {
16523 * alert('Failed: ' + reason);
16524 * });
16525 * ```
16526 *
16527 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
16528 *
16529 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise.
16530 *
16531 * However, the more traditional CommonJS-style usage is still available, and documented below.
16532 *
16533 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
16534 * interface for interacting with an object that represents the result of an action that is
16535 * performed asynchronously, and may or may not be finished at any given point in time.
16536 *
16537 * From the perspective of dealing with error handling, deferred and promise APIs are to
16538 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
16539 *
16540 * ```js
16541 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
16542 * // are available in the current lexical scope (they could have been injected or passed in).
16543 *
16544 * function asyncGreet(name) {
16545 * var deferred = $q.defer();
16546 *
16547 * setTimeout(function() {
16548 * deferred.notify('About to greet ' + name + '.');
16549 *
16550 * if (okToGreet(name)) {
16551 * deferred.resolve('Hello, ' + name + '!');
16552 * } else {
16553 * deferred.reject('Greeting ' + name + ' is not allowed.');
16554 * }
16555 * }, 1000);
16556 *
16557 * return deferred.promise;
16558 * }
16559 *
16560 * var promise = asyncGreet('Robin Hood');
16561 * promise.then(function(greeting) {
16562 * alert('Success: ' + greeting);
16563 * }, function(reason) {
16564 * alert('Failed: ' + reason);
16565 * }, function(update) {
16566 * alert('Got notification: ' + update);
16567 * });
16568 * ```
16569 *
16570 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
16571 * comes in the way of guarantees that promise and deferred APIs make, see
16572 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
16573 *
16574 * Additionally the promise api allows for composition that is very hard to do with the
16575 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
16576 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
16577 * section on serial or parallel joining of promises.
16578 *
16579 * # The Deferred API
16580 *
16581 * A new instance of deferred is constructed by calling `$q.defer()`.
16582 *
16583 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
16584 * that can be used for signaling the successful or unsuccessful completion, as well as the status
16585 * of the task.
16586 *
16587 * **Methods**
16588 *
16589 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
16590 * constructed via `$q.reject`, the promise will be rejected instead.
16591 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
16592 * resolving it with a rejection constructed via `$q.reject`.
16593 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
16594 * multiple times before the promise is either resolved or rejected.
16595 *
16596 * **Properties**
16597 *
16598 * - promise – `{Promise}` – promise object associated with this deferred.
16599 *
16600 *
16601 * # The Promise API
16602 *
16603 * A new promise instance is created when a deferred instance is created and can be retrieved by
16604 * calling `deferred.promise`.
16605 *
16606 * The purpose of the promise object is to allow for interested parties to get access to the result
16607 * of the deferred task when it completes.
16608 *
16609 * **Methods**
16610 *
16611 * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or
16612 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
16613 * as soon as the result is available. The callbacks are called with a single argument: the result
16614 * or rejection reason. Additionally, the notify callback may be called zero or more times to
16615 * provide a progress indication, before the promise is resolved or rejected.
16616 *
16617 * This method *returns a new promise* which is resolved or rejected via the return value of the
16618 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
16619 * with the value which is resolved in that promise using
16620 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
16621 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
16622 * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback
16623 * arguments are optional.
16624 *
16625 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
16626 *
16627 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
16628 * but to do so without modifying the final value. This is useful to release resources or do some
16629 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
16630 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
16631 * more information.
16632 *
16633 * # Chaining promises
16634 *
16635 * Because calling the `then` method of a promise returns a new derived promise, it is easily
16636 * possible to create a chain of promises:
16637 *
16638 * ```js
16639 * promiseB = promiseA.then(function(result) {
16640 * return result + 1;
16641 * });
16642 *
16643 * // promiseB will be resolved immediately after promiseA is resolved and its value
16644 * // will be the result of promiseA incremented by 1
16645 * ```
16646 *
16647 * It is possible to create chains of any length and since a promise can be resolved with another
16648 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
16649 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
16650 * $http's response interceptors.
16651 *
16652 *
16653 * # Differences between Kris Kowal's Q and $q
16654 *
16655 * There are two main differences:
16656 *
16657 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
16658 * mechanism in angular, which means faster propagation of resolution or rejection into your
16659 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
16660 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
16661 * all the important functionality needed for common async tasks.
16662 *
16663 * # Testing
16664 *
16665 * ```js
16666 * it('should simulate promise', inject(function($q, $rootScope) {
16667 * var deferred = $q.defer();
16668 * var promise = deferred.promise;
16669 * var resolvedValue;
16670 *
16671 * promise.then(function(value) { resolvedValue = value; });
16672 * expect(resolvedValue).toBeUndefined();
16673 *
16674 * // Simulate resolving of promise
16675 * deferred.resolve(123);
16676 * // Note that the 'then' function does not get called synchronously.
16677 * // This is because we want the promise API to always be async, whether or not
16678 * // it got called synchronously or asynchronously.
16679 * expect(resolvedValue).toBeUndefined();
16680 *
16681 * // Propagate promise resolution to 'then' functions using $apply().
16682 * $rootScope.$apply();
16683 * expect(resolvedValue).toEqual(123);
16684 * }));
16685 * ```
16686 *
16687 * @param {function(function, function)} resolver Function which is responsible for resolving or
16688 * rejecting the newly created promise. The first parameter is a function which resolves the
16689 * promise, the second parameter is a function which rejects the promise.
16690 *
16691 * @returns {Promise} The newly created promise.
16692 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070016693/**
16694 * @ngdoc provider
16695 * @name $qProvider
16696 * @this
16697 *
16698 * @description
16699 */
Ed Tanous904063f2017-03-02 16:48:24 -080016700function $QProvider() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016701 var errorOnUnhandledRejections = true;
Ed Tanous904063f2017-03-02 16:48:24 -080016702 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
16703 return qFactory(function(callback) {
16704 $rootScope.$evalAsync(callback);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016705 }, $exceptionHandler, errorOnUnhandledRejections);
Ed Tanous904063f2017-03-02 16:48:24 -080016706 }];
Ed Tanous4758d5b2017-06-06 15:28:13 -070016707
16708 /**
16709 * @ngdoc method
16710 * @name $qProvider#errorOnUnhandledRejections
16711 * @kind function
16712 *
16713 * @description
16714 * Retrieves or overrides whether to generate an error when a rejected promise is not handled.
16715 * This feature is enabled by default.
16716 *
16717 * @param {boolean=} value Whether to generate an error when a rejected promise is not handled.
16718 * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for
16719 * chaining otherwise.
16720 */
16721 this.errorOnUnhandledRejections = function(value) {
16722 if (isDefined(value)) {
16723 errorOnUnhandledRejections = value;
16724 return this;
16725 } else {
16726 return errorOnUnhandledRejections;
16727 }
16728 };
Ed Tanous904063f2017-03-02 16:48:24 -080016729}
16730
Ed Tanous4758d5b2017-06-06 15:28:13 -070016731/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080016732function $$QProvider() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016733 var errorOnUnhandledRejections = true;
Ed Tanous904063f2017-03-02 16:48:24 -080016734 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
16735 return qFactory(function(callback) {
16736 $browser.defer(callback);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016737 }, $exceptionHandler, errorOnUnhandledRejections);
Ed Tanous904063f2017-03-02 16:48:24 -080016738 }];
Ed Tanous4758d5b2017-06-06 15:28:13 -070016739
16740 this.errorOnUnhandledRejections = function(value) {
16741 if (isDefined(value)) {
16742 errorOnUnhandledRejections = value;
16743 return this;
16744 } else {
16745 return errorOnUnhandledRejections;
16746 }
16747 };
Ed Tanous904063f2017-03-02 16:48:24 -080016748}
16749
16750/**
16751 * Constructs a promise manager.
16752 *
16753 * @param {function(function)} nextTick Function for executing functions in the next turn.
16754 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
16755 * debugging purposes.
Ed Tanous4758d5b2017-06-06 15:28:13 -070016756 @ param {=boolean} errorOnUnhandledRejections Whether an error should be generated on unhandled
16757 * promises rejections.
Ed Tanous904063f2017-03-02 16:48:24 -080016758 * @returns {object} Promise manager.
16759 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070016760function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
Ed Tanous904063f2017-03-02 16:48:24 -080016761 var $qMinErr = minErr('$q', TypeError);
Ed Tanous4758d5b2017-06-06 15:28:13 -070016762 var queueSize = 0;
16763 var checkQueue = [];
Ed Tanous904063f2017-03-02 16:48:24 -080016764
16765 /**
16766 * @ngdoc method
16767 * @name ng.$q#defer
16768 * @kind function
16769 *
16770 * @description
16771 * Creates a `Deferred` object which represents a task which will finish in the future.
16772 *
16773 * @returns {Deferred} Returns a new instance of deferred.
16774 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070016775 function defer() {
16776 return new Deferred();
16777 }
16778
16779 function Deferred() {
16780 var promise = this.promise = new Promise();
16781 //Non prototype methods necessary to support unbound execution :/
16782 this.resolve = function(val) { resolvePromise(promise, val); };
16783 this.reject = function(reason) { rejectPromise(promise, reason); };
16784 this.notify = function(progress) { notifyPromise(promise, progress); };
16785 }
16786
Ed Tanous904063f2017-03-02 16:48:24 -080016787
16788 function Promise() {
16789 this.$$state = { status: 0 };
16790 }
16791
16792 extend(Promise.prototype, {
16793 then: function(onFulfilled, onRejected, progressBack) {
16794 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
16795 return this;
16796 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070016797 var result = new Promise();
Ed Tanous904063f2017-03-02 16:48:24 -080016798
16799 this.$$state.pending = this.$$state.pending || [];
16800 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
16801 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
16802
Ed Tanous4758d5b2017-06-06 15:28:13 -070016803 return result;
Ed Tanous904063f2017-03-02 16:48:24 -080016804 },
16805
Ed Tanous4758d5b2017-06-06 15:28:13 -070016806 'catch': function(callback) {
Ed Tanous904063f2017-03-02 16:48:24 -080016807 return this.then(null, callback);
16808 },
16809
Ed Tanous4758d5b2017-06-06 15:28:13 -070016810 'finally': function(callback, progressBack) {
Ed Tanous904063f2017-03-02 16:48:24 -080016811 return this.then(function(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016812 return handleCallback(value, resolve, callback);
Ed Tanous904063f2017-03-02 16:48:24 -080016813 }, function(error) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016814 return handleCallback(error, reject, callback);
Ed Tanous904063f2017-03-02 16:48:24 -080016815 }, progressBack);
16816 }
16817 });
16818
Ed Tanous904063f2017-03-02 16:48:24 -080016819 function processQueue(state) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016820 var fn, promise, pending;
Ed Tanous904063f2017-03-02 16:48:24 -080016821
16822 pending = state.pending;
16823 state.processScheduled = false;
16824 state.pending = undefined;
Ed Tanous4758d5b2017-06-06 15:28:13 -070016825 try {
16826 for (var i = 0, ii = pending.length; i < ii; ++i) {
16827 state.pur = true;
16828 promise = pending[i][0];
16829 fn = pending[i][state.status];
16830 try {
16831 if (isFunction(fn)) {
16832 resolvePromise(promise, fn(state.value));
16833 } else if (state.status === 1) {
16834 resolvePromise(promise, state.value);
16835 } else {
16836 rejectPromise(promise, state.value);
16837 }
16838 } catch (e) {
16839 rejectPromise(promise, e);
Ed Tanous904063f2017-03-02 16:48:24 -080016840 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070016841 }
16842 } finally {
16843 --queueSize;
16844 if (errorOnUnhandledRejections && queueSize === 0) {
16845 nextTick(processChecks);
16846 }
16847 }
16848 }
16849
16850 function processChecks() {
16851 // eslint-disable-next-line no-unmodified-loop-condition
16852 while (!queueSize && checkQueue.length) {
16853 var toCheck = checkQueue.shift();
16854 if (!toCheck.pur) {
16855 toCheck.pur = true;
16856 var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
16857 if (toCheck.value instanceof Error) {
16858 exceptionHandler(toCheck.value, errorMessage);
16859 } else {
16860 exceptionHandler(errorMessage);
16861 }
Ed Tanous904063f2017-03-02 16:48:24 -080016862 }
16863 }
16864 }
16865
16866 function scheduleProcessQueue(state) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016867 if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !state.pur) {
16868 if (queueSize === 0 && checkQueue.length === 0) {
16869 nextTick(processChecks);
16870 }
16871 checkQueue.push(state);
16872 }
Ed Tanous904063f2017-03-02 16:48:24 -080016873 if (state.processScheduled || !state.pending) return;
16874 state.processScheduled = true;
Ed Tanous4758d5b2017-06-06 15:28:13 -070016875 ++queueSize;
Ed Tanous904063f2017-03-02 16:48:24 -080016876 nextTick(function() { processQueue(state); });
16877 }
16878
Ed Tanous4758d5b2017-06-06 15:28:13 -070016879 function resolvePromise(promise, val) {
16880 if (promise.$$state.status) return;
16881 if (val === promise) {
16882 $$reject(promise, $qMinErr(
16883 'qcycle',
16884 'Expected promise to be resolved with value other than itself \'{0}\'',
16885 val));
16886 } else {
16887 $$resolve(promise, val);
16888 }
16889
Ed Tanous904063f2017-03-02 16:48:24 -080016890 }
16891
Ed Tanous4758d5b2017-06-06 15:28:13 -070016892 function $$resolve(promise, val) {
16893 var then;
16894 var done = false;
16895 try {
16896 if (isObject(val) || isFunction(val)) then = val.then;
16897 if (isFunction(then)) {
16898 promise.$$state.status = -1;
16899 then.call(val, doResolve, doReject, doNotify);
Ed Tanous904063f2017-03-02 16:48:24 -080016900 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070016901 promise.$$state.value = val;
16902 promise.$$state.status = 1;
16903 scheduleProcessQueue(promise.$$state);
Ed Tanous904063f2017-03-02 16:48:24 -080016904 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070016905 } catch (e) {
16906 doReject(e);
Ed Tanous904063f2017-03-02 16:48:24 -080016907 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070016908
16909 function doResolve(val) {
16910 if (done) return;
16911 done = true;
16912 $$resolve(promise, val);
16913 }
16914 function doReject(val) {
16915 if (done) return;
16916 done = true;
16917 $$reject(promise, val);
16918 }
16919 function doNotify(progress) {
16920 notifyPromise(promise, progress);
16921 }
16922 }
16923
16924 function rejectPromise(promise, reason) {
16925 if (promise.$$state.status) return;
16926 $$reject(promise, reason);
16927 }
16928
16929 function $$reject(promise, reason) {
16930 promise.$$state.value = reason;
16931 promise.$$state.status = 2;
16932 scheduleProcessQueue(promise.$$state);
16933 }
16934
16935 function notifyPromise(promise, progress) {
16936 var callbacks = promise.$$state.pending;
16937
16938 if ((promise.$$state.status <= 0) && callbacks && callbacks.length) {
16939 nextTick(function() {
16940 var callback, result;
16941 for (var i = 0, ii = callbacks.length; i < ii; i++) {
16942 result = callbacks[i][0];
16943 callback = callbacks[i][3];
16944 try {
16945 notifyPromise(result, isFunction(callback) ? callback(progress) : progress);
16946 } catch (e) {
16947 exceptionHandler(e);
16948 }
16949 }
16950 });
16951 }
16952 }
Ed Tanous904063f2017-03-02 16:48:24 -080016953
16954 /**
16955 * @ngdoc method
16956 * @name $q#reject
16957 * @kind function
16958 *
16959 * @description
16960 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
16961 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
16962 * a promise chain, you don't need to worry about it.
16963 *
16964 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
16965 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
16966 * a promise error callback and you want to forward the error to the promise derived from the
16967 * current promise, you have to "rethrow" the error by returning a rejection constructed via
16968 * `reject`.
16969 *
16970 * ```js
16971 * promiseB = promiseA.then(function(result) {
16972 * // success: do something and resolve promiseB
16973 * // with the old or a new result
16974 * return result;
16975 * }, function(reason) {
16976 * // error: handle the error if possible and
16977 * // resolve promiseB with newPromiseOrValue,
16978 * // otherwise forward the rejection to promiseB
16979 * if (canHandle(reason)) {
16980 * // handle the error and recover
16981 * return newPromiseOrValue;
16982 * }
16983 * return $q.reject(reason);
16984 * });
16985 * ```
16986 *
16987 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
16988 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
16989 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070016990 function reject(reason) {
16991 var result = new Promise();
16992 rejectPromise(result, reason);
16993 return result;
16994 }
Ed Tanous904063f2017-03-02 16:48:24 -080016995
Ed Tanous4758d5b2017-06-06 15:28:13 -070016996 function handleCallback(value, resolver, callback) {
Ed Tanous904063f2017-03-02 16:48:24 -080016997 var callbackOutput = null;
16998 try {
16999 if (isFunction(callback)) callbackOutput = callback();
17000 } catch (e) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017001 return reject(e);
Ed Tanous904063f2017-03-02 16:48:24 -080017002 }
17003 if (isPromiseLike(callbackOutput)) {
17004 return callbackOutput.then(function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017005 return resolver(value);
17006 }, reject);
Ed Tanous904063f2017-03-02 16:48:24 -080017007 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017008 return resolver(value);
Ed Tanous904063f2017-03-02 16:48:24 -080017009 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070017010 }
Ed Tanous904063f2017-03-02 16:48:24 -080017011
17012 /**
17013 * @ngdoc method
17014 * @name $q#when
17015 * @kind function
17016 *
17017 * @description
17018 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
17019 * This is useful when you are dealing with an object that might or might not be a promise, or if
17020 * the promise comes from a source that can't be trusted.
17021 *
17022 * @param {*} value Value or a promise
17023 * @param {Function=} successCallback
17024 * @param {Function=} errorCallback
17025 * @param {Function=} progressCallback
17026 * @returns {Promise} Returns a promise of the passed value or promise
17027 */
17028
17029
Ed Tanous4758d5b2017-06-06 15:28:13 -070017030 function when(value, callback, errback, progressBack) {
17031 var result = new Promise();
17032 resolvePromise(result, value);
17033 return result.then(callback, errback, progressBack);
17034 }
Ed Tanous904063f2017-03-02 16:48:24 -080017035
17036 /**
17037 * @ngdoc method
17038 * @name $q#resolve
17039 * @kind function
17040 *
17041 * @description
17042 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
17043 *
17044 * @param {*} value Value or a promise
17045 * @param {Function=} successCallback
17046 * @param {Function=} errorCallback
17047 * @param {Function=} progressCallback
17048 * @returns {Promise} Returns a promise of the passed value or promise
17049 */
17050 var resolve = when;
17051
17052 /**
17053 * @ngdoc method
17054 * @name $q#all
17055 * @kind function
17056 *
17057 * @description
17058 * Combines multiple promises into a single promise that is resolved when all of the input
17059 * promises are resolved.
17060 *
17061 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
17062 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
17063 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
17064 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
17065 * with the same rejection value.
17066 */
17067
17068 function all(promises) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017069 var result = new Promise(),
Ed Tanous904063f2017-03-02 16:48:24 -080017070 counter = 0,
17071 results = isArray(promises) ? [] : {};
17072
17073 forEach(promises, function(promise, key) {
17074 counter++;
17075 when(promise).then(function(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080017076 results[key] = value;
Ed Tanous4758d5b2017-06-06 15:28:13 -070017077 if (!(--counter)) resolvePromise(result, results);
Ed Tanous904063f2017-03-02 16:48:24 -080017078 }, function(reason) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017079 rejectPromise(result, reason);
Ed Tanous904063f2017-03-02 16:48:24 -080017080 });
17081 });
17082
17083 if (counter === 0) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017084 resolvePromise(result, results);
Ed Tanous904063f2017-03-02 16:48:24 -080017085 }
17086
Ed Tanous4758d5b2017-06-06 15:28:13 -070017087 return result;
Ed Tanous904063f2017-03-02 16:48:24 -080017088 }
17089
17090 /**
17091 * @ngdoc method
17092 * @name $q#race
17093 * @kind function
17094 *
17095 * @description
17096 * Returns a promise that resolves or rejects as soon as one of those promises
17097 * resolves or rejects, with the value or reason from that promise.
17098 *
17099 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
17100 * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises`
17101 * resolves or rejects, with the value or reason from that promise.
17102 */
17103
17104 function race(promises) {
17105 var deferred = defer();
17106
17107 forEach(promises, function(promise) {
17108 when(promise).then(deferred.resolve, deferred.reject);
17109 });
17110
17111 return deferred.promise;
17112 }
17113
Ed Tanous4758d5b2017-06-06 15:28:13 -070017114 function $Q(resolver) {
Ed Tanous904063f2017-03-02 16:48:24 -080017115 if (!isFunction(resolver)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017116 throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver);
Ed Tanous904063f2017-03-02 16:48:24 -080017117 }
17118
Ed Tanous4758d5b2017-06-06 15:28:13 -070017119 var promise = new Promise();
Ed Tanous904063f2017-03-02 16:48:24 -080017120
17121 function resolveFn(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017122 resolvePromise(promise, value);
Ed Tanous904063f2017-03-02 16:48:24 -080017123 }
17124
17125 function rejectFn(reason) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017126 rejectPromise(promise, reason);
Ed Tanous904063f2017-03-02 16:48:24 -080017127 }
17128
17129 resolver(resolveFn, rejectFn);
17130
Ed Tanous4758d5b2017-06-06 15:28:13 -070017131 return promise;
17132 }
Ed Tanous904063f2017-03-02 16:48:24 -080017133
17134 // Let's make the instanceof operator work for promises, so that
17135 // `new $q(fn) instanceof $q` would evaluate to true.
17136 $Q.prototype = Promise.prototype;
17137
17138 $Q.defer = defer;
17139 $Q.reject = reject;
17140 $Q.when = when;
17141 $Q.resolve = resolve;
17142 $Q.all = all;
17143 $Q.race = race;
17144
17145 return $Q;
17146}
17147
Ed Tanous4758d5b2017-06-06 15:28:13 -070017148/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080017149function $$RAFProvider() { //rAF
17150 this.$get = ['$window', '$timeout', function($window, $timeout) {
17151 var requestAnimationFrame = $window.requestAnimationFrame ||
17152 $window.webkitRequestAnimationFrame;
17153
17154 var cancelAnimationFrame = $window.cancelAnimationFrame ||
17155 $window.webkitCancelAnimationFrame ||
17156 $window.webkitCancelRequestAnimationFrame;
17157
17158 var rafSupported = !!requestAnimationFrame;
17159 var raf = rafSupported
17160 ? function(fn) {
17161 var id = requestAnimationFrame(fn);
17162 return function() {
17163 cancelAnimationFrame(id);
17164 };
17165 }
17166 : function(fn) {
17167 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
17168 return function() {
17169 $timeout.cancel(timer);
17170 };
17171 };
17172
17173 raf.supported = rafSupported;
17174
17175 return raf;
17176 }];
17177}
17178
17179/**
17180 * DESIGN NOTES
17181 *
17182 * The design decisions behind the scope are heavily favored for speed and memory consumption.
17183 *
17184 * The typical use of scope is to watch the expressions, which most of the time return the same
17185 * value as last time so we optimize the operation.
17186 *
17187 * Closures construction is expensive in terms of speed as well as memory:
17188 * - No closures, instead use prototypical inheritance for API
17189 * - Internal state needs to be stored on scope directly, which means that private state is
17190 * exposed as $$____ properties
17191 *
17192 * Loop operations are optimized by using while(count--) { ... }
17193 * - This means that in order to keep the same order of execution as addition we have to add
17194 * items to the array at the beginning (unshift) instead of at the end (push)
17195 *
17196 * Child scopes are created and removed often
17197 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
17198 *
17199 * There are fewer watches than observers. This is why you don't want the observer to be implemented
17200 * in the same way as watch. Watch requires return of the initialization function which is expensive
17201 * to construct.
17202 */
17203
17204
17205/**
17206 * @ngdoc provider
17207 * @name $rootScopeProvider
17208 * @description
17209 *
17210 * Provider for the $rootScope service.
17211 */
17212
17213/**
17214 * @ngdoc method
17215 * @name $rootScopeProvider#digestTtl
17216 * @description
17217 *
17218 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
17219 * assuming that the model is unstable.
17220 *
17221 * The current default is 10 iterations.
17222 *
17223 * In complex applications it's possible that the dependencies between `$watch`s will result in
17224 * several digest iterations. However if an application needs more than the default 10 digest
17225 * iterations for its model to stabilize then you should investigate what is causing the model to
17226 * continuously change during the digest.
17227 *
17228 * Increasing the TTL could have performance implications, so you should not change it without
17229 * proper justification.
17230 *
17231 * @param {number} limit The number of digest iterations.
17232 */
17233
17234
17235/**
17236 * @ngdoc service
17237 * @name $rootScope
Ed Tanous4758d5b2017-06-06 15:28:13 -070017238 * @this
17239 *
Ed Tanous904063f2017-03-02 16:48:24 -080017240 * @description
17241 *
17242 * Every application has a single root {@link ng.$rootScope.Scope scope}.
17243 * All other scopes are descendant scopes of the root scope. Scopes provide separation
17244 * between the model and the view, via a mechanism for watching the model for changes.
17245 * They also provide event emission/broadcast and subscription facility. See the
17246 * {@link guide/scope developer guide on scopes}.
17247 */
17248function $RootScopeProvider() {
17249 var TTL = 10;
17250 var $rootScopeMinErr = minErr('$rootScope');
17251 var lastDirtyWatch = null;
17252 var applyAsyncId = null;
17253
17254 this.digestTtl = function(value) {
17255 if (arguments.length) {
17256 TTL = value;
17257 }
17258 return TTL;
17259 };
17260
17261 function createChildScopeClass(parent) {
17262 function ChildScope() {
17263 this.$$watchers = this.$$nextSibling =
17264 this.$$childHead = this.$$childTail = null;
17265 this.$$listeners = {};
17266 this.$$listenerCount = {};
17267 this.$$watchersCount = 0;
17268 this.$id = nextUid();
17269 this.$$ChildScope = null;
17270 }
17271 ChildScope.prototype = parent;
17272 return ChildScope;
17273 }
17274
17275 this.$get = ['$exceptionHandler', '$parse', '$browser',
17276 function($exceptionHandler, $parse, $browser) {
17277
17278 function destroyChildScope($event) {
17279 $event.currentScope.$$destroyed = true;
17280 }
17281
17282 function cleanUpScope($scope) {
17283
Ed Tanous4758d5b2017-06-06 15:28:13 -070017284 // Support: IE 9 only
Ed Tanous904063f2017-03-02 16:48:24 -080017285 if (msie === 9) {
17286 // There is a memory leak in IE9 if all child scopes are not disconnected
17287 // completely when a scope is destroyed. So this code will recurse up through
17288 // all this scopes children
17289 //
17290 // See issue https://github.com/angular/angular.js/issues/10706
Ed Tanous4758d5b2017-06-06 15:28:13 -070017291 if ($scope.$$childHead) {
17292 cleanUpScope($scope.$$childHead);
17293 }
17294 if ($scope.$$nextSibling) {
17295 cleanUpScope($scope.$$nextSibling);
17296 }
Ed Tanous904063f2017-03-02 16:48:24 -080017297 }
17298
17299 // The code below works around IE9 and V8's memory leaks
17300 //
17301 // See:
17302 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
17303 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
17304 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
17305
17306 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
17307 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
17308 }
17309
17310 /**
17311 * @ngdoc type
17312 * @name $rootScope.Scope
17313 *
17314 * @description
17315 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
17316 * {@link auto.$injector $injector}. Child scopes are created using the
17317 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
17318 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
17319 * an in-depth introduction and usage examples.
17320 *
17321 *
17322 * # Inheritance
17323 * A scope can inherit from a parent scope, as in this example:
17324 * ```js
17325 var parent = $rootScope;
17326 var child = parent.$new();
17327
17328 parent.salutation = "Hello";
17329 expect(child.salutation).toEqual('Hello');
17330
17331 child.salutation = "Welcome";
17332 expect(child.salutation).toEqual('Welcome');
17333 expect(parent.salutation).toEqual('Hello');
17334 * ```
17335 *
17336 * When interacting with `Scope` in tests, additional helper methods are available on the
17337 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
17338 * details.
17339 *
17340 *
17341 * @param {Object.<string, function()>=} providers Map of service factory which need to be
17342 * provided for the current scope. Defaults to {@link ng}.
17343 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
17344 * append/override services provided by `providers`. This is handy
17345 * when unit-testing and having the need to override a default
17346 * service.
17347 * @returns {Object} Newly created scope.
17348 *
17349 */
17350 function Scope() {
17351 this.$id = nextUid();
17352 this.$$phase = this.$parent = this.$$watchers =
17353 this.$$nextSibling = this.$$prevSibling =
17354 this.$$childHead = this.$$childTail = null;
17355 this.$root = this;
17356 this.$$destroyed = false;
17357 this.$$listeners = {};
17358 this.$$listenerCount = {};
17359 this.$$watchersCount = 0;
17360 this.$$isolateBindings = null;
17361 }
17362
17363 /**
17364 * @ngdoc property
17365 * @name $rootScope.Scope#$id
17366 *
17367 * @description
17368 * Unique scope ID (monotonically increasing) useful for debugging.
17369 */
17370
17371 /**
17372 * @ngdoc property
17373 * @name $rootScope.Scope#$parent
17374 *
17375 * @description
17376 * Reference to the parent scope.
17377 */
17378
17379 /**
17380 * @ngdoc property
17381 * @name $rootScope.Scope#$root
17382 *
17383 * @description
17384 * Reference to the root scope.
17385 */
17386
17387 Scope.prototype = {
17388 constructor: Scope,
17389 /**
17390 * @ngdoc method
17391 * @name $rootScope.Scope#$new
17392 * @kind function
17393 *
17394 * @description
17395 * Creates a new child {@link ng.$rootScope.Scope scope}.
17396 *
17397 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
17398 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
17399 *
17400 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
17401 * desired for the scope and its child scopes to be permanently detached from the parent and
17402 * thus stop participating in model change detection and listener notification by invoking.
17403 *
17404 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
17405 * parent scope. The scope is isolated, as it can not see parent scope properties.
17406 * When creating widgets, it is useful for the widget to not accidentally read parent
17407 * state.
17408 *
17409 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
17410 * of the newly created scope. Defaults to `this` scope if not provided.
17411 * This is used when creating a transclude scope to correctly place it
17412 * in the scope hierarchy while maintaining the correct prototypical
17413 * inheritance.
17414 *
17415 * @returns {Object} The newly created child scope.
17416 *
17417 */
17418 $new: function(isolate, parent) {
17419 var child;
17420
17421 parent = parent || this;
17422
17423 if (isolate) {
17424 child = new Scope();
17425 child.$root = this.$root;
17426 } else {
17427 // Only create a child scope class if somebody asks for one,
17428 // but cache it to allow the VM to optimize lookups.
17429 if (!this.$$ChildScope) {
17430 this.$$ChildScope = createChildScopeClass(this);
17431 }
17432 child = new this.$$ChildScope();
17433 }
17434 child.$parent = parent;
17435 child.$$prevSibling = parent.$$childTail;
17436 if (parent.$$childHead) {
17437 parent.$$childTail.$$nextSibling = child;
17438 parent.$$childTail = child;
17439 } else {
17440 parent.$$childHead = parent.$$childTail = child;
17441 }
17442
17443 // When the new scope is not isolated or we inherit from `this`, and
17444 // the parent scope is destroyed, the property `$$destroyed` is inherited
17445 // prototypically. In all other cases, this property needs to be set
17446 // when the parent scope is destroyed.
17447 // The listener needs to be added after the parent is set
Ed Tanous4758d5b2017-06-06 15:28:13 -070017448 if (isolate || parent !== this) child.$on('$destroy', destroyChildScope);
Ed Tanous904063f2017-03-02 16:48:24 -080017449
17450 return child;
17451 },
17452
17453 /**
17454 * @ngdoc method
17455 * @name $rootScope.Scope#$watch
17456 * @kind function
17457 *
17458 * @description
17459 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
17460 *
17461 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
17462 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
17463 * its value when executed multiple times with the same input because it may be executed multiple
17464 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
Ed Tanous4758d5b2017-06-06 15:28:13 -070017465 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).)
Ed Tanous904063f2017-03-02 16:48:24 -080017466 * - The `listener` is called only when the value from the current `watchExpression` and the
17467 * previous call to `watchExpression` are not equal (with the exception of the initial run,
17468 * see below). Inequality is determined according to reference inequality,
17469 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
17470 * via the `!==` Javascript operator, unless `objectEquality == true`
17471 * (see next point)
17472 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
17473 * according to the {@link angular.equals} function. To save the value of the object for
17474 * later comparison, the {@link angular.copy} function is used. This therefore means that
17475 * watching complex objects will have adverse memory and performance implications.
Ed Tanous4758d5b2017-06-06 15:28:13 -070017476 * - This should not be used to watch for changes in objects that are
17477 * or contain [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
Ed Tanous904063f2017-03-02 16:48:24 -080017478 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
17479 * This is achieved by rerunning the watchers until no changes are detected. The rerun
17480 * iteration limit is 10 to prevent an infinite loop deadlock.
17481 *
17482 *
17483 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
17484 * you can register a `watchExpression` function with no `listener`. (Be prepared for
17485 * multiple calls to your `watchExpression` because it will execute multiple times in a
17486 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
17487 *
17488 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
17489 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
17490 * watcher. In rare cases, this is undesirable because the listener is called when the result
17491 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
17492 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
17493 * listener was called due to initialization.
17494 *
17495 *
17496 *
17497 * # Example
17498 * ```js
17499 // let's assume that scope was dependency injected as the $rootScope
17500 var scope = $rootScope;
17501 scope.name = 'misko';
17502 scope.counter = 0;
17503
17504 expect(scope.counter).toEqual(0);
17505 scope.$watch('name', function(newValue, oldValue) {
17506 scope.counter = scope.counter + 1;
17507 });
17508 expect(scope.counter).toEqual(0);
17509
17510 scope.$digest();
17511 // the listener is always called during the first $digest loop after it was registered
17512 expect(scope.counter).toEqual(1);
17513
17514 scope.$digest();
17515 // but now it will not be called unless the value changes
17516 expect(scope.counter).toEqual(1);
17517
17518 scope.name = 'adam';
17519 scope.$digest();
17520 expect(scope.counter).toEqual(2);
17521
17522
17523
17524 // Using a function as a watchExpression
17525 var food;
17526 scope.foodCounter = 0;
17527 expect(scope.foodCounter).toEqual(0);
17528 scope.$watch(
17529 // This function returns the value being watched. It is called for each turn of the $digest loop
17530 function() { return food; },
17531 // This is the change listener, called when the value returned from the above function changes
17532 function(newValue, oldValue) {
17533 if ( newValue !== oldValue ) {
17534 // Only increment the counter if the value changed
17535 scope.foodCounter = scope.foodCounter + 1;
17536 }
17537 }
17538 );
17539 // No digest has been run so the counter will be zero
17540 expect(scope.foodCounter).toEqual(0);
17541
17542 // Run the digest but since food has not changed count will still be zero
17543 scope.$digest();
17544 expect(scope.foodCounter).toEqual(0);
17545
17546 // Update food and run digest. Now the counter will increment
17547 food = 'cheeseburger';
17548 scope.$digest();
17549 expect(scope.foodCounter).toEqual(1);
17550
17551 * ```
17552 *
17553 *
17554 *
17555 * @param {(function()|string)} watchExpression Expression that is evaluated on each
17556 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
17557 * a call to the `listener`.
17558 *
17559 * - `string`: Evaluated as {@link guide/expression expression}
17560 * - `function(scope)`: called with current `scope` as a parameter.
17561 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
17562 * of `watchExpression` changes.
17563 *
17564 * - `newVal` contains the current value of the `watchExpression`
17565 * - `oldVal` contains the previous value of the `watchExpression`
17566 * - `scope` refers to the current scope
17567 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
17568 * comparing for reference equality.
17569 * @returns {function()} Returns a deregistration function for this listener.
17570 */
17571 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
17572 var get = $parse(watchExp);
17573
17574 if (get.$$watchDelegate) {
17575 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
17576 }
17577 var scope = this,
17578 array = scope.$$watchers,
17579 watcher = {
17580 fn: listener,
17581 last: initWatchVal,
17582 get: get,
17583 exp: prettyPrintExpression || watchExp,
17584 eq: !!objectEquality
17585 };
17586
17587 lastDirtyWatch = null;
17588
17589 if (!isFunction(listener)) {
17590 watcher.fn = noop;
17591 }
17592
17593 if (!array) {
17594 array = scope.$$watchers = [];
Ed Tanous4758d5b2017-06-06 15:28:13 -070017595 array.$$digestWatchIndex = -1;
Ed Tanous904063f2017-03-02 16:48:24 -080017596 }
17597 // we use unshift since we use a while loop in $digest for speed.
17598 // the while loop reads in reverse order.
17599 array.unshift(watcher);
Ed Tanous4758d5b2017-06-06 15:28:13 -070017600 array.$$digestWatchIndex++;
Ed Tanous904063f2017-03-02 16:48:24 -080017601 incrementWatchersCount(this, 1);
17602
17603 return function deregisterWatch() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017604 var index = arrayRemove(array, watcher);
17605 if (index >= 0) {
Ed Tanous904063f2017-03-02 16:48:24 -080017606 incrementWatchersCount(scope, -1);
Ed Tanous4758d5b2017-06-06 15:28:13 -070017607 if (index < array.$$digestWatchIndex) {
17608 array.$$digestWatchIndex--;
17609 }
Ed Tanous904063f2017-03-02 16:48:24 -080017610 }
17611 lastDirtyWatch = null;
17612 };
17613 },
17614
17615 /**
17616 * @ngdoc method
17617 * @name $rootScope.Scope#$watchGroup
17618 * @kind function
17619 *
17620 * @description
17621 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
17622 * If any one expression in the collection changes the `listener` is executed.
17623 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070017624 * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return
17625 * values are examined for changes on every call to `$digest`.
Ed Tanous904063f2017-03-02 16:48:24 -080017626 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
17627 *
17628 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
17629 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
17630 *
17631 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
17632 * expression in `watchExpressions` changes
17633 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
17634 * those of `watchExpression`
17635 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
17636 * those of `watchExpression`
17637 * The `scope` refers to the current scope.
17638 * @returns {function()} Returns a de-registration function for all listeners.
17639 */
17640 $watchGroup: function(watchExpressions, listener) {
17641 var oldValues = new Array(watchExpressions.length);
17642 var newValues = new Array(watchExpressions.length);
17643 var deregisterFns = [];
17644 var self = this;
17645 var changeReactionScheduled = false;
17646 var firstRun = true;
17647
17648 if (!watchExpressions.length) {
17649 // No expressions means we call the listener ASAP
17650 var shouldCall = true;
17651 self.$evalAsync(function() {
17652 if (shouldCall) listener(newValues, newValues, self);
17653 });
17654 return function deregisterWatchGroup() {
17655 shouldCall = false;
17656 };
17657 }
17658
17659 if (watchExpressions.length === 1) {
17660 // Special case size of one
17661 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
17662 newValues[0] = value;
17663 oldValues[0] = oldValue;
17664 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
17665 });
17666 }
17667
17668 forEach(watchExpressions, function(expr, i) {
17669 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
17670 newValues[i] = value;
17671 oldValues[i] = oldValue;
17672 if (!changeReactionScheduled) {
17673 changeReactionScheduled = true;
17674 self.$evalAsync(watchGroupAction);
17675 }
17676 });
17677 deregisterFns.push(unwatchFn);
17678 });
17679
17680 function watchGroupAction() {
17681 changeReactionScheduled = false;
17682
17683 if (firstRun) {
17684 firstRun = false;
17685 listener(newValues, newValues, self);
17686 } else {
17687 listener(newValues, oldValues, self);
17688 }
17689 }
17690
17691 return function deregisterWatchGroup() {
17692 while (deregisterFns.length) {
17693 deregisterFns.shift()();
17694 }
17695 };
17696 },
17697
17698
17699 /**
17700 * @ngdoc method
17701 * @name $rootScope.Scope#$watchCollection
17702 * @kind function
17703 *
17704 * @description
17705 * Shallow watches the properties of an object and fires whenever any of the properties change
17706 * (for arrays, this implies watching the array items; for object maps, this implies watching
17707 * the properties). If a change is detected, the `listener` callback is fired.
17708 *
17709 * - The `obj` collection is observed via standard $watch operation and is examined on every
17710 * call to $digest() to see if any items have been added, removed, or moved.
17711 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
17712 * adding, removing, and moving items belonging to an object or array.
17713 *
17714 *
17715 * # Example
17716 * ```js
17717 $scope.names = ['igor', 'matias', 'misko', 'james'];
17718 $scope.dataCount = 4;
17719
17720 $scope.$watchCollection('names', function(newNames, oldNames) {
17721 $scope.dataCount = newNames.length;
17722 });
17723
17724 expect($scope.dataCount).toEqual(4);
17725 $scope.$digest();
17726
17727 //still at 4 ... no changes
17728 expect($scope.dataCount).toEqual(4);
17729
17730 $scope.names.pop();
17731 $scope.$digest();
17732
17733 //now there's been a change
17734 expect($scope.dataCount).toEqual(3);
17735 * ```
17736 *
17737 *
17738 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
17739 * expression value should evaluate to an object or an array which is observed on each
17740 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
17741 * collection will trigger a call to the `listener`.
17742 *
17743 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
17744 * when a change is detected.
17745 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
17746 * - The `oldCollection` object is a copy of the former collection data.
17747 * Due to performance considerations, the`oldCollection` value is computed only if the
17748 * `listener` function declares two or more arguments.
17749 * - The `scope` argument refers to the current scope.
17750 *
17751 * @returns {function()} Returns a de-registration function for this listener. When the
17752 * de-registration function is executed, the internal watch operation is terminated.
17753 */
17754 $watchCollection: function(obj, listener) {
17755 $watchCollectionInterceptor.$stateful = true;
17756
17757 var self = this;
17758 // the current value, updated on each dirty-check run
17759 var newValue;
17760 // a shallow copy of the newValue from the last dirty-check run,
17761 // updated to match newValue during dirty-check run
17762 var oldValue;
17763 // a shallow copy of the newValue from when the last change happened
17764 var veryOldValue;
17765 // only track veryOldValue if the listener is asking for it
17766 var trackVeryOldValue = (listener.length > 1);
17767 var changeDetected = 0;
17768 var changeDetector = $parse(obj, $watchCollectionInterceptor);
17769 var internalArray = [];
17770 var internalObject = {};
17771 var initRun = true;
17772 var oldLength = 0;
17773
17774 function $watchCollectionInterceptor(_value) {
17775 newValue = _value;
17776 var newLength, key, bothNaN, newItem, oldItem;
17777
17778 // If the new value is undefined, then return undefined as the watch may be a one-time watch
17779 if (isUndefined(newValue)) return;
17780
17781 if (!isObject(newValue)) { // if primitive
17782 if (oldValue !== newValue) {
17783 oldValue = newValue;
17784 changeDetected++;
17785 }
17786 } else if (isArrayLike(newValue)) {
17787 if (oldValue !== internalArray) {
17788 // we are transitioning from something which was not an array into array.
17789 oldValue = internalArray;
17790 oldLength = oldValue.length = 0;
17791 changeDetected++;
17792 }
17793
17794 newLength = newValue.length;
17795
17796 if (oldLength !== newLength) {
17797 // if lengths do not match we need to trigger change notification
17798 changeDetected++;
17799 oldValue.length = oldLength = newLength;
17800 }
17801 // copy the items to oldValue and look for changes.
17802 for (var i = 0; i < newLength; i++) {
17803 oldItem = oldValue[i];
17804 newItem = newValue[i];
17805
Ed Tanous4758d5b2017-06-06 15:28:13 -070017806 // eslint-disable-next-line no-self-compare
Ed Tanous904063f2017-03-02 16:48:24 -080017807 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
17808 if (!bothNaN && (oldItem !== newItem)) {
17809 changeDetected++;
17810 oldValue[i] = newItem;
17811 }
17812 }
17813 } else {
17814 if (oldValue !== internalObject) {
17815 // we are transitioning from something which was not an object into object.
17816 oldValue = internalObject = {};
17817 oldLength = 0;
17818 changeDetected++;
17819 }
17820 // copy the items to oldValue and look for changes.
17821 newLength = 0;
17822 for (key in newValue) {
17823 if (hasOwnProperty.call(newValue, key)) {
17824 newLength++;
17825 newItem = newValue[key];
17826 oldItem = oldValue[key];
17827
17828 if (key in oldValue) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017829 // eslint-disable-next-line no-self-compare
Ed Tanous904063f2017-03-02 16:48:24 -080017830 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
17831 if (!bothNaN && (oldItem !== newItem)) {
17832 changeDetected++;
17833 oldValue[key] = newItem;
17834 }
17835 } else {
17836 oldLength++;
17837 oldValue[key] = newItem;
17838 changeDetected++;
17839 }
17840 }
17841 }
17842 if (oldLength > newLength) {
17843 // we used to have more keys, need to find them and destroy them.
17844 changeDetected++;
17845 for (key in oldValue) {
17846 if (!hasOwnProperty.call(newValue, key)) {
17847 oldLength--;
17848 delete oldValue[key];
17849 }
17850 }
17851 }
17852 }
17853 return changeDetected;
17854 }
17855
17856 function $watchCollectionAction() {
17857 if (initRun) {
17858 initRun = false;
17859 listener(newValue, newValue, self);
17860 } else {
17861 listener(newValue, veryOldValue, self);
17862 }
17863
17864 // make a copy for the next time a collection is changed
17865 if (trackVeryOldValue) {
17866 if (!isObject(newValue)) {
17867 //primitive
17868 veryOldValue = newValue;
17869 } else if (isArrayLike(newValue)) {
17870 veryOldValue = new Array(newValue.length);
17871 for (var i = 0; i < newValue.length; i++) {
17872 veryOldValue[i] = newValue[i];
17873 }
17874 } else { // if object
17875 veryOldValue = {};
17876 for (var key in newValue) {
17877 if (hasOwnProperty.call(newValue, key)) {
17878 veryOldValue[key] = newValue[key];
17879 }
17880 }
17881 }
17882 }
17883 }
17884
17885 return this.$watch(changeDetector, $watchCollectionAction);
17886 },
17887
17888 /**
17889 * @ngdoc method
17890 * @name $rootScope.Scope#$digest
17891 * @kind function
17892 *
17893 * @description
17894 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
17895 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
17896 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
17897 * until no more listeners are firing. This means that it is possible to get into an infinite
17898 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
17899 * iterations exceeds 10.
17900 *
17901 * Usually, you don't call `$digest()` directly in
17902 * {@link ng.directive:ngController controllers} or in
17903 * {@link ng.$compileProvider#directive directives}.
17904 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
17905 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
17906 *
17907 * If you want to be notified whenever `$digest()` is called,
17908 * you can register a `watchExpression` function with
17909 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
17910 *
17911 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
17912 *
17913 * # Example
17914 * ```js
17915 var scope = ...;
17916 scope.name = 'misko';
17917 scope.counter = 0;
17918
17919 expect(scope.counter).toEqual(0);
17920 scope.$watch('name', function(newValue, oldValue) {
17921 scope.counter = scope.counter + 1;
17922 });
17923 expect(scope.counter).toEqual(0);
17924
17925 scope.$digest();
17926 // the listener is always called during the first $digest loop after it was registered
17927 expect(scope.counter).toEqual(1);
17928
17929 scope.$digest();
17930 // but now it will not be called unless the value changes
17931 expect(scope.counter).toEqual(1);
17932
17933 scope.name = 'adam';
17934 scope.$digest();
17935 expect(scope.counter).toEqual(2);
17936 * ```
17937 *
17938 */
17939 $digest: function() {
17940 var watch, value, last, fn, get,
17941 watchers,
Ed Tanous904063f2017-03-02 16:48:24 -080017942 dirty, ttl = TTL,
17943 next, current, target = this,
17944 watchLog = [],
17945 logIdx, asyncTask;
17946
17947 beginPhase('$digest');
17948 // Check for changes to browser url that happened in sync before the call to $digest
17949 $browser.$$checkUrlChange();
17950
17951 if (this === $rootScope && applyAsyncId !== null) {
17952 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
17953 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
17954 $browser.defer.cancel(applyAsyncId);
17955 flushApplyAsync();
17956 }
17957
17958 lastDirtyWatch = null;
17959
17960 do { // "while dirty" loop
17961 dirty = false;
17962 current = target;
17963
17964 // It's safe for asyncQueuePosition to be a local variable here because this loop can't
Ed Tanous4758d5b2017-06-06 15:28:13 -070017965 // be reentered recursively. Calling $digest from a function passed to $evalAsync would
Ed Tanous904063f2017-03-02 16:48:24 -080017966 // lead to a '$digest already in progress' error.
17967 for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) {
17968 try {
17969 asyncTask = asyncQueue[asyncQueuePosition];
Ed Tanous4758d5b2017-06-06 15:28:13 -070017970 fn = asyncTask.fn;
17971 fn(asyncTask.scope, asyncTask.locals);
Ed Tanous904063f2017-03-02 16:48:24 -080017972 } catch (e) {
17973 $exceptionHandler(e);
17974 }
17975 lastDirtyWatch = null;
17976 }
17977 asyncQueue.length = 0;
17978
17979 traverseScopesLoop:
17980 do { // "traverse the scopes" loop
17981 if ((watchers = current.$$watchers)) {
17982 // process our watches
Ed Tanous4758d5b2017-06-06 15:28:13 -070017983 watchers.$$digestWatchIndex = watchers.length;
17984 while (watchers.$$digestWatchIndex--) {
Ed Tanous904063f2017-03-02 16:48:24 -080017985 try {
Ed Tanous4758d5b2017-06-06 15:28:13 -070017986 watch = watchers[watchers.$$digestWatchIndex];
Ed Tanous904063f2017-03-02 16:48:24 -080017987 // Most common watches are on primitives, in which case we can short
17988 // circuit it with === operator, only when === fails do we use .equals
17989 if (watch) {
17990 get = watch.get;
17991 if ((value = get(current)) !== (last = watch.last) &&
17992 !(watch.eq
17993 ? equals(value, last)
Ed Tanous4758d5b2017-06-06 15:28:13 -070017994 : (isNumberNaN(value) && isNumberNaN(last)))) {
Ed Tanous904063f2017-03-02 16:48:24 -080017995 dirty = true;
17996 lastDirtyWatch = watch;
17997 watch.last = watch.eq ? copy(value, null) : value;
17998 fn = watch.fn;
17999 fn(value, ((last === initWatchVal) ? value : last), current);
18000 if (ttl < 5) {
18001 logIdx = 4 - ttl;
18002 if (!watchLog[logIdx]) watchLog[logIdx] = [];
18003 watchLog[logIdx].push({
18004 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
18005 newVal: value,
18006 oldVal: last
18007 });
18008 }
18009 } else if (watch === lastDirtyWatch) {
18010 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
18011 // have already been tested.
18012 dirty = false;
18013 break traverseScopesLoop;
18014 }
18015 }
18016 } catch (e) {
18017 $exceptionHandler(e);
18018 }
18019 }
18020 }
18021
18022 // Insanity Warning: scope depth-first traversal
18023 // yes, this code is a bit crazy, but it works and we have tests to prove it!
18024 // this piece should be kept in sync with the traversal in $broadcast
18025 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
18026 (current !== target && current.$$nextSibling)))) {
18027 while (current !== target && !(next = current.$$nextSibling)) {
18028 current = current.$parent;
18029 }
18030 }
18031 } while ((current = next));
18032
18033 // `break traverseScopesLoop;` takes us to here
18034
18035 if ((dirty || asyncQueue.length) && !(ttl--)) {
18036 clearPhase();
18037 throw $rootScopeMinErr('infdig',
18038 '{0} $digest() iterations reached. Aborting!\n' +
18039 'Watchers fired in the last 5 iterations: {1}',
18040 TTL, watchLog);
18041 }
18042
18043 } while (dirty || asyncQueue.length);
18044
18045 clearPhase();
18046
18047 // postDigestQueuePosition isn't local here because this loop can be reentered recursively.
18048 while (postDigestQueuePosition < postDigestQueue.length) {
18049 try {
18050 postDigestQueue[postDigestQueuePosition++]();
18051 } catch (e) {
18052 $exceptionHandler(e);
18053 }
18054 }
18055 postDigestQueue.length = postDigestQueuePosition = 0;
Ed Tanous4758d5b2017-06-06 15:28:13 -070018056
18057 // Check for changes to browser url that happened during the $digest
18058 // (for which no event is fired; e.g. via `history.pushState()`)
18059 $browser.$$checkUrlChange();
Ed Tanous904063f2017-03-02 16:48:24 -080018060 },
18061
18062
18063 /**
18064 * @ngdoc event
18065 * @name $rootScope.Scope#$destroy
18066 * @eventType broadcast on scope being destroyed
18067 *
18068 * @description
18069 * Broadcasted when a scope and its children are being destroyed.
18070 *
18071 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
18072 * clean up DOM bindings before an element is removed from the DOM.
18073 */
18074
18075 /**
18076 * @ngdoc method
18077 * @name $rootScope.Scope#$destroy
18078 * @kind function
18079 *
18080 * @description
18081 * Removes the current scope (and all of its children) from the parent scope. Removal implies
18082 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
18083 * propagate to the current scope and its children. Removal also implies that the current
18084 * scope is eligible for garbage collection.
18085 *
18086 * The `$destroy()` is usually used by directives such as
18087 * {@link ng.directive:ngRepeat ngRepeat} for managing the
18088 * unrolling of the loop.
18089 *
18090 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
18091 * Application code can register a `$destroy` event handler that will give it a chance to
18092 * perform any necessary cleanup.
18093 *
18094 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
18095 * clean up DOM bindings before an element is removed from the DOM.
18096 */
18097 $destroy: function() {
18098 // We can't destroy a scope that has been already destroyed.
18099 if (this.$$destroyed) return;
18100 var parent = this.$parent;
18101
18102 this.$broadcast('$destroy');
18103 this.$$destroyed = true;
18104
18105 if (this === $rootScope) {
18106 //Remove handlers attached to window when $rootScope is removed
18107 $browser.$$applicationDestroyed();
18108 }
18109
18110 incrementWatchersCount(this, -this.$$watchersCount);
18111 for (var eventName in this.$$listenerCount) {
18112 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
18113 }
18114
18115 // sever all the references to parent scopes (after this cleanup, the current scope should
18116 // not be retained by any of our references and should be eligible for garbage collection)
Ed Tanous4758d5b2017-06-06 15:28:13 -070018117 if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling;
18118 if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling;
Ed Tanous904063f2017-03-02 16:48:24 -080018119 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
18120 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
18121
18122 // Disable listeners, watchers and apply/digest methods
18123 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
18124 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
18125 this.$$listeners = {};
18126
18127 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
18128 this.$$nextSibling = null;
18129 cleanUpScope(this);
18130 },
18131
18132 /**
18133 * @ngdoc method
18134 * @name $rootScope.Scope#$eval
18135 * @kind function
18136 *
18137 * @description
18138 * Executes the `expression` on the current scope and returns the result. Any exceptions in
18139 * the expression are propagated (uncaught). This is useful when evaluating Angular
18140 * expressions.
18141 *
18142 * # Example
18143 * ```js
18144 var scope = ng.$rootScope.Scope();
18145 scope.a = 1;
18146 scope.b = 2;
18147
18148 expect(scope.$eval('a+b')).toEqual(3);
18149 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
18150 * ```
18151 *
18152 * @param {(string|function())=} expression An angular expression to be executed.
18153 *
18154 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18155 * - `function(scope)`: execute the function with the current `scope` parameter.
18156 *
18157 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
18158 * @returns {*} The result of evaluating the expression.
18159 */
18160 $eval: function(expr, locals) {
18161 return $parse(expr)(this, locals);
18162 },
18163
18164 /**
18165 * @ngdoc method
18166 * @name $rootScope.Scope#$evalAsync
18167 * @kind function
18168 *
18169 * @description
18170 * Executes the expression on the current scope at a later point in time.
18171 *
18172 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
18173 * that:
18174 *
18175 * - it will execute after the function that scheduled the evaluation (preferably before DOM
18176 * rendering).
18177 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
18178 * `expression` execution.
18179 *
18180 * Any exceptions from the execution of the expression are forwarded to the
18181 * {@link ng.$exceptionHandler $exceptionHandler} service.
18182 *
18183 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
18184 * will be scheduled. However, it is encouraged to always call code that changes the model
18185 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
18186 *
18187 * @param {(string|function())=} expression An angular expression to be executed.
18188 *
18189 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18190 * - `function(scope)`: execute the function with the current `scope` parameter.
18191 *
18192 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
18193 */
18194 $evalAsync: function(expr, locals) {
18195 // if we are outside of an $digest loop and this is the first time we are scheduling async
18196 // task also schedule async auto-flush
18197 if (!$rootScope.$$phase && !asyncQueue.length) {
18198 $browser.defer(function() {
18199 if (asyncQueue.length) {
18200 $rootScope.$digest();
18201 }
18202 });
18203 }
18204
Ed Tanous4758d5b2017-06-06 15:28:13 -070018205 asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
Ed Tanous904063f2017-03-02 16:48:24 -080018206 },
18207
18208 $$postDigest: function(fn) {
18209 postDigestQueue.push(fn);
18210 },
18211
18212 /**
18213 * @ngdoc method
18214 * @name $rootScope.Scope#$apply
18215 * @kind function
18216 *
18217 * @description
18218 * `$apply()` is used to execute an expression in angular from outside of the angular
18219 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
18220 * Because we are calling into the angular framework we need to perform proper scope life
18221 * cycle of {@link ng.$exceptionHandler exception handling},
18222 * {@link ng.$rootScope.Scope#$digest executing watches}.
18223 *
18224 * ## Life cycle
18225 *
18226 * # Pseudo-Code of `$apply()`
18227 * ```js
18228 function $apply(expr) {
18229 try {
18230 return $eval(expr);
18231 } catch (e) {
18232 $exceptionHandler(e);
18233 } finally {
18234 $root.$digest();
18235 }
18236 }
18237 * ```
18238 *
18239 *
18240 * Scope's `$apply()` method transitions through the following stages:
18241 *
18242 * 1. The {@link guide/expression expression} is executed using the
18243 * {@link ng.$rootScope.Scope#$eval $eval()} method.
18244 * 2. Any exceptions from the execution of the expression are forwarded to the
18245 * {@link ng.$exceptionHandler $exceptionHandler} service.
18246 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
18247 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
18248 *
18249 *
18250 * @param {(string|function())=} exp An angular expression to be executed.
18251 *
18252 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18253 * - `function(scope)`: execute the function with current `scope` parameter.
18254 *
18255 * @returns {*} The result of evaluating the expression.
18256 */
18257 $apply: function(expr) {
18258 try {
18259 beginPhase('$apply');
18260 try {
18261 return this.$eval(expr);
18262 } finally {
18263 clearPhase();
18264 }
18265 } catch (e) {
18266 $exceptionHandler(e);
18267 } finally {
18268 try {
18269 $rootScope.$digest();
18270 } catch (e) {
18271 $exceptionHandler(e);
Ed Tanous4758d5b2017-06-06 15:28:13 -070018272 // eslint-disable-next-line no-unsafe-finally
Ed Tanous904063f2017-03-02 16:48:24 -080018273 throw e;
18274 }
18275 }
18276 },
18277
18278 /**
18279 * @ngdoc method
18280 * @name $rootScope.Scope#$applyAsync
18281 * @kind function
18282 *
18283 * @description
18284 * Schedule the invocation of $apply to occur at a later time. The actual time difference
18285 * varies across browsers, but is typically around ~10 milliseconds.
18286 *
18287 * This can be used to queue up multiple expressions which need to be evaluated in the same
18288 * digest.
18289 *
18290 * @param {(string|function())=} exp An angular expression to be executed.
18291 *
18292 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18293 * - `function(scope)`: execute the function with current `scope` parameter.
18294 */
18295 $applyAsync: function(expr) {
18296 var scope = this;
Ed Tanous4758d5b2017-06-06 15:28:13 -070018297 if (expr) {
18298 applyAsyncQueue.push($applyAsyncExpression);
18299 }
Ed Tanous904063f2017-03-02 16:48:24 -080018300 expr = $parse(expr);
18301 scheduleApplyAsync();
18302
18303 function $applyAsyncExpression() {
18304 scope.$eval(expr);
18305 }
18306 },
18307
18308 /**
18309 * @ngdoc method
18310 * @name $rootScope.Scope#$on
18311 * @kind function
18312 *
18313 * @description
18314 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
18315 * discussion of event life cycle.
18316 *
18317 * The event listener function format is: `function(event, args...)`. The `event` object
18318 * passed into the listener has the following attributes:
18319 *
18320 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
18321 * `$broadcast`-ed.
18322 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
18323 * event propagates through the scope hierarchy, this property is set to null.
18324 * - `name` - `{string}`: name of the event.
18325 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
18326 * further event propagation (available only for events that were `$emit`-ed).
18327 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
18328 * to true.
18329 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
18330 *
18331 * @param {string} name Event name to listen on.
18332 * @param {function(event, ...args)} listener Function to call when the event is emitted.
18333 * @returns {function()} Returns a deregistration function for this listener.
18334 */
18335 $on: function(name, listener) {
18336 var namedListeners = this.$$listeners[name];
18337 if (!namedListeners) {
18338 this.$$listeners[name] = namedListeners = [];
18339 }
18340 namedListeners.push(listener);
18341
18342 var current = this;
18343 do {
18344 if (!current.$$listenerCount[name]) {
18345 current.$$listenerCount[name] = 0;
18346 }
18347 current.$$listenerCount[name]++;
18348 } while ((current = current.$parent));
18349
18350 var self = this;
18351 return function() {
18352 var indexOfListener = namedListeners.indexOf(listener);
18353 if (indexOfListener !== -1) {
18354 namedListeners[indexOfListener] = null;
18355 decrementListenerCount(self, 1, name);
18356 }
18357 };
18358 },
18359
18360
18361 /**
18362 * @ngdoc method
18363 * @name $rootScope.Scope#$emit
18364 * @kind function
18365 *
18366 * @description
18367 * Dispatches an event `name` upwards through the scope hierarchy notifying the
18368 * registered {@link ng.$rootScope.Scope#$on} listeners.
18369 *
18370 * The event life cycle starts at the scope on which `$emit` was called. All
18371 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
18372 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
18373 * registered listeners along the way. The event will stop propagating if one of the listeners
18374 * cancels it.
18375 *
18376 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
18377 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
18378 *
18379 * @param {string} name Event name to emit.
18380 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
18381 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
18382 */
18383 $emit: function(name, args) {
18384 var empty = [],
18385 namedListeners,
18386 scope = this,
18387 stopPropagation = false,
18388 event = {
18389 name: name,
18390 targetScope: scope,
18391 stopPropagation: function() {stopPropagation = true;},
18392 preventDefault: function() {
18393 event.defaultPrevented = true;
18394 },
18395 defaultPrevented: false
18396 },
18397 listenerArgs = concat([event], arguments, 1),
18398 i, length;
18399
18400 do {
18401 namedListeners = scope.$$listeners[name] || empty;
18402 event.currentScope = scope;
18403 for (i = 0, length = namedListeners.length; i < length; i++) {
18404
18405 // if listeners were deregistered, defragment the array
18406 if (!namedListeners[i]) {
18407 namedListeners.splice(i, 1);
18408 i--;
18409 length--;
18410 continue;
18411 }
18412 try {
18413 //allow all listeners attached to the current scope to run
18414 namedListeners[i].apply(null, listenerArgs);
18415 } catch (e) {
18416 $exceptionHandler(e);
18417 }
18418 }
18419 //if any listener on the current scope stops propagation, prevent bubbling
18420 if (stopPropagation) {
18421 event.currentScope = null;
18422 return event;
18423 }
18424 //traverse upwards
18425 scope = scope.$parent;
18426 } while (scope);
18427
18428 event.currentScope = null;
18429
18430 return event;
18431 },
18432
18433
18434 /**
18435 * @ngdoc method
18436 * @name $rootScope.Scope#$broadcast
18437 * @kind function
18438 *
18439 * @description
18440 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
18441 * registered {@link ng.$rootScope.Scope#$on} listeners.
18442 *
18443 * The event life cycle starts at the scope on which `$broadcast` was called. All
18444 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
18445 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
18446 * scope and calls all registered listeners along the way. The event cannot be canceled.
18447 *
18448 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
18449 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
18450 *
18451 * @param {string} name Event name to broadcast.
18452 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
18453 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
18454 */
18455 $broadcast: function(name, args) {
18456 var target = this,
18457 current = target,
18458 next = target,
18459 event = {
18460 name: name,
18461 targetScope: target,
18462 preventDefault: function() {
18463 event.defaultPrevented = true;
18464 },
18465 defaultPrevented: false
18466 };
18467
18468 if (!target.$$listenerCount[name]) return event;
18469
18470 var listenerArgs = concat([event], arguments, 1),
18471 listeners, i, length;
18472
18473 //down while you can, then up and next sibling or up and next sibling until back at root
18474 while ((current = next)) {
18475 event.currentScope = current;
18476 listeners = current.$$listeners[name] || [];
18477 for (i = 0, length = listeners.length; i < length; i++) {
18478 // if listeners were deregistered, defragment the array
18479 if (!listeners[i]) {
18480 listeners.splice(i, 1);
18481 i--;
18482 length--;
18483 continue;
18484 }
18485
18486 try {
18487 listeners[i].apply(null, listenerArgs);
18488 } catch (e) {
18489 $exceptionHandler(e);
18490 }
18491 }
18492
18493 // Insanity Warning: scope depth-first traversal
18494 // yes, this code is a bit crazy, but it works and we have tests to prove it!
18495 // this piece should be kept in sync with the traversal in $digest
18496 // (though it differs due to having the extra check for $$listenerCount)
18497 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
18498 (current !== target && current.$$nextSibling)))) {
18499 while (current !== target && !(next = current.$$nextSibling)) {
18500 current = current.$parent;
18501 }
18502 }
18503 }
18504
18505 event.currentScope = null;
18506 return event;
18507 }
18508 };
18509
18510 var $rootScope = new Scope();
18511
18512 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
18513 var asyncQueue = $rootScope.$$asyncQueue = [];
18514 var postDigestQueue = $rootScope.$$postDigestQueue = [];
18515 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
18516
18517 var postDigestQueuePosition = 0;
18518
18519 return $rootScope;
18520
18521
18522 function beginPhase(phase) {
18523 if ($rootScope.$$phase) {
18524 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
18525 }
18526
18527 $rootScope.$$phase = phase;
18528 }
18529
18530 function clearPhase() {
18531 $rootScope.$$phase = null;
18532 }
18533
18534 function incrementWatchersCount(current, count) {
18535 do {
18536 current.$$watchersCount += count;
18537 } while ((current = current.$parent));
18538 }
18539
18540 function decrementListenerCount(current, count, name) {
18541 do {
18542 current.$$listenerCount[name] -= count;
18543
18544 if (current.$$listenerCount[name] === 0) {
18545 delete current.$$listenerCount[name];
18546 }
18547 } while ((current = current.$parent));
18548 }
18549
18550 /**
18551 * function used as an initial value for watchers.
18552 * because it's unique we can easily tell it apart from other values
18553 */
18554 function initWatchVal() {}
18555
18556 function flushApplyAsync() {
18557 while (applyAsyncQueue.length) {
18558 try {
18559 applyAsyncQueue.shift()();
18560 } catch (e) {
18561 $exceptionHandler(e);
18562 }
18563 }
18564 applyAsyncId = null;
18565 }
18566
18567 function scheduleApplyAsync() {
18568 if (applyAsyncId === null) {
18569 applyAsyncId = $browser.defer(function() {
18570 $rootScope.$apply(flushApplyAsync);
18571 });
18572 }
18573 }
18574 }];
18575}
18576
18577/**
18578 * @ngdoc service
18579 * @name $rootElement
18580 *
18581 * @description
18582 * The root element of Angular application. This is either the element where {@link
18583 * ng.directive:ngApp ngApp} was declared or the element passed into
18584 * {@link angular.bootstrap}. The element represents the root element of application. It is also the
18585 * location where the application's {@link auto.$injector $injector} service gets
18586 * published, and can be retrieved using `$rootElement.injector()`.
18587 */
18588
18589
18590// the implementation is in angular.bootstrap
18591
18592/**
Ed Tanous4758d5b2017-06-06 15:28:13 -070018593 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080018594 * @description
18595 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
18596 */
18597function $$SanitizeUriProvider() {
18598 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
18599 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
18600
18601 /**
18602 * @description
18603 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
18604 * urls during a[href] sanitization.
18605 *
18606 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
18607 *
18608 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
18609 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
18610 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
18611 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
18612 *
18613 * @param {RegExp=} regexp New regexp to whitelist urls with.
18614 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
18615 * chaining otherwise.
18616 */
18617 this.aHrefSanitizationWhitelist = function(regexp) {
18618 if (isDefined(regexp)) {
18619 aHrefSanitizationWhitelist = regexp;
18620 return this;
18621 }
18622 return aHrefSanitizationWhitelist;
18623 };
18624
18625
18626 /**
18627 * @description
18628 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
18629 * urls during img[src] sanitization.
18630 *
18631 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
18632 *
18633 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
18634 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
18635 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
18636 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
18637 *
18638 * @param {RegExp=} regexp New regexp to whitelist urls with.
18639 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
18640 * chaining otherwise.
18641 */
18642 this.imgSrcSanitizationWhitelist = function(regexp) {
18643 if (isDefined(regexp)) {
18644 imgSrcSanitizationWhitelist = regexp;
18645 return this;
18646 }
18647 return imgSrcSanitizationWhitelist;
18648 };
18649
18650 this.$get = function() {
18651 return function sanitizeUri(uri, isImage) {
18652 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
18653 var normalizedVal;
18654 normalizedVal = urlResolve(uri).href;
18655 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
18656 return 'unsafe:' + normalizedVal;
18657 }
18658 return uri;
18659 };
18660 };
18661}
18662
18663/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
18664 * Any commits to this file should be reviewed with security in mind. *
18665 * Changes to this file can potentially create security vulnerabilities. *
18666 * An approval from 2 Core members with history of modifying *
18667 * this file is required. *
18668 * *
18669 * Does the change somehow allow for arbitrary javascript to be executed? *
18670 * Or allows for someone to change the prototype of built-in objects? *
18671 * Or gives undesired access to variables likes document or window? *
18672 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
18673
Ed Tanous4758d5b2017-06-06 15:28:13 -070018674/* exported $SceProvider, $SceDelegateProvider */
18675
Ed Tanous904063f2017-03-02 16:48:24 -080018676var $sceMinErr = minErr('$sce');
18677
18678var SCE_CONTEXTS = {
Ed Tanous4758d5b2017-06-06 15:28:13 -070018679 // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding).
Ed Tanous904063f2017-03-02 16:48:24 -080018680 HTML: 'html',
Ed Tanous4758d5b2017-06-06 15:28:13 -070018681
18682 // Style statements or stylesheets. Currently unused in AngularJS.
Ed Tanous904063f2017-03-02 16:48:24 -080018683 CSS: 'css',
Ed Tanous4758d5b2017-06-06 15:28:13 -070018684
18685 // An URL used in a context where it does not refer to a resource that loads code. Currently
18686 // unused in AngularJS.
Ed Tanous904063f2017-03-02 16:48:24 -080018687 URL: 'url',
Ed Tanous4758d5b2017-06-06 15:28:13 -070018688
18689 // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
18690 // code. (e.g. ng-include, script src binding, templateUrl)
Ed Tanous904063f2017-03-02 16:48:24 -080018691 RESOURCE_URL: 'resourceUrl',
Ed Tanous4758d5b2017-06-06 15:28:13 -070018692
18693 // Script. Currently unused in AngularJS.
Ed Tanous904063f2017-03-02 16:48:24 -080018694 JS: 'js'
18695};
18696
18697// Helper functions follow.
18698
Ed Tanous4758d5b2017-06-06 15:28:13 -070018699var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
18700
18701function snakeToCamel(name) {
18702 return name
18703 .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
18704}
18705
Ed Tanous904063f2017-03-02 16:48:24 -080018706function adjustMatcher(matcher) {
18707 if (matcher === 'self') {
18708 return matcher;
18709 } else if (isString(matcher)) {
18710 // Strings match exactly except for 2 wildcards - '*' and '**'.
18711 // '*' matches any character except those from the set ':/.?&'.
18712 // '**' matches any character (like .* in a RegExp).
18713 // More than 2 *'s raises an error as it's ill defined.
18714 if (matcher.indexOf('***') > -1) {
18715 throw $sceMinErr('iwcard',
18716 'Illegal sequence *** in string matcher. String: {0}', matcher);
18717 }
18718 matcher = escapeForRegexp(matcher).
Ed Tanous4758d5b2017-06-06 15:28:13 -070018719 replace(/\\\*\\\*/g, '.*').
18720 replace(/\\\*/g, '[^:/.?&;]*');
Ed Tanous904063f2017-03-02 16:48:24 -080018721 return new RegExp('^' + matcher + '$');
18722 } else if (isRegExp(matcher)) {
18723 // The only other type of matcher allowed is a Regexp.
18724 // Match entire URL / disallow partial matches.
18725 // Flags are reset (i.e. no global, ignoreCase or multiline)
18726 return new RegExp('^' + matcher.source + '$');
18727 } else {
18728 throw $sceMinErr('imatcher',
18729 'Matchers may only be "self", string patterns or RegExp objects');
18730 }
18731}
18732
18733
18734function adjustMatchers(matchers) {
18735 var adjustedMatchers = [];
18736 if (isDefined(matchers)) {
18737 forEach(matchers, function(matcher) {
18738 adjustedMatchers.push(adjustMatcher(matcher));
18739 });
18740 }
18741 return adjustedMatchers;
18742}
18743
18744
18745/**
18746 * @ngdoc service
18747 * @name $sceDelegate
18748 * @kind function
18749 *
18750 * @description
18751 *
18752 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
18753 * Contextual Escaping (SCE)} services to AngularJS.
18754 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070018755 * For an overview of this service and the functionnality it provides in AngularJS, see the main
18756 * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how
18757 * SCE works in their application, which shouldn't be needed in most cases.
18758 *
18759 * <div class="alert alert-danger">
18760 * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or
18761 * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners,
18762 * changes to this service will also influence users, so be extra careful and document your changes.
18763 * </div>
18764 *
Ed Tanous904063f2017-03-02 16:48:24 -080018765 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
18766 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
18767 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
18768 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
18769 * work because `$sce` delegates to `$sceDelegate` for these operations.
18770 *
18771 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
18772 *
18773 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
18774 * can override it completely to change the behavior of `$sce`, the common case would
18775 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
18776 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
18777 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
18778 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
18779 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
18780 */
18781
18782/**
18783 * @ngdoc provider
18784 * @name $sceDelegateProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070018785 * @this
18786 *
Ed Tanous904063f2017-03-02 16:48:24 -080018787 * @description
18788 *
18789 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
Ed Tanous4758d5b2017-06-06 15:28:13 -070018790 * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}.
18791 *
18792 * The `$sceDelegateProvider` allows one to get/set the whitelists and blacklists used to ensure
18793 * that the URLs used for sourcing AngularJS templates and other script-running URLs are safe (all
18794 * places that use the `$sce.RESOURCE_URL` context). See
18795 * {@link ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist}
18796 * and
18797 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist},
Ed Tanous904063f2017-03-02 16:48:24 -080018798 *
18799 * For the general details about this service in Angular, read the main page for {@link ng.$sce
18800 * Strict Contextual Escaping (SCE)}.
18801 *
18802 * **Example**: Consider the following case. <a name="example"></a>
18803 *
18804 * - your app is hosted at url `http://myapp.example.com/`
18805 * - but some of your templates are hosted on other domains you control such as
Ed Tanous4758d5b2017-06-06 15:28:13 -070018806 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
Ed Tanous904063f2017-03-02 16:48:24 -080018807 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
18808 *
18809 * Here is what a secure configuration for this scenario might look like:
18810 *
18811 * ```
18812 * angular.module('myApp', []).config(function($sceDelegateProvider) {
18813 * $sceDelegateProvider.resourceUrlWhitelist([
18814 * // Allow same origin resource loads.
18815 * 'self',
18816 * // Allow loading from our assets domain. Notice the difference between * and **.
18817 * 'http://srv*.assets.example.com/**'
18818 * ]);
18819 *
18820 * // The blacklist overrides the whitelist so the open redirect here is blocked.
18821 * $sceDelegateProvider.resourceUrlBlacklist([
18822 * 'http://myapp.example.com/clickThru**'
18823 * ]);
18824 * });
18825 * ```
Ed Tanous4758d5b2017-06-06 15:28:13 -070018826 * Note that an empty whitelist will block every resource URL from being loaded, and will require
18827 * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates
18828 * requested by {@link ng.$templateRequest $templateRequest} that are present in
18829 * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism
18830 * to populate your templates in that cache at config time, then it is a good idea to remove 'self'
18831 * from that whitelist. This helps to mitigate the security impact of certain types of issues, like
18832 * for instance attacker-controlled `ng-includes`.
Ed Tanous904063f2017-03-02 16:48:24 -080018833 */
18834
18835function $SceDelegateProvider() {
18836 this.SCE_CONTEXTS = SCE_CONTEXTS;
18837
18838 // Resource URLs can also be trusted by policy.
18839 var resourceUrlWhitelist = ['self'],
18840 resourceUrlBlacklist = [];
18841
18842 /**
18843 * @ngdoc method
18844 * @name $sceDelegateProvider#resourceUrlWhitelist
18845 * @kind function
18846 *
18847 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
Ed Tanous4758d5b2017-06-06 15:28:13 -070018848 * provided. This must be an array or null. A snapshot of this array is used so further
18849 * changes to the array are ignored.
18850 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
18851 * allowed in this array.
Ed Tanous904063f2017-03-02 16:48:24 -080018852 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070018853 * @return {Array} The currently set whitelist array.
Ed Tanous904063f2017-03-02 16:48:24 -080018854 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070018855 * @description
18856 * Sets/Gets the whitelist of trusted resource URLs.
Ed Tanous904063f2017-03-02 16:48:24 -080018857 *
18858 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
18859 * same origin resource requests.
18860 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070018861 * <div class="alert alert-warning">
18862 * **Note:** the default whitelist of 'self' is not recommended if your app shares its origin
18863 * with other apps! It is a good idea to limit it to only your application's directory.
18864 * </div>
Ed Tanous904063f2017-03-02 16:48:24 -080018865 */
18866 this.resourceUrlWhitelist = function(value) {
18867 if (arguments.length) {
18868 resourceUrlWhitelist = adjustMatchers(value);
18869 }
18870 return resourceUrlWhitelist;
18871 };
18872
18873 /**
18874 * @ngdoc method
18875 * @name $sceDelegateProvider#resourceUrlBlacklist
18876 * @kind function
18877 *
18878 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
Ed Tanous4758d5b2017-06-06 15:28:13 -070018879 * provided. This must be an array or null. A snapshot of this array is used so further
18880 * changes to the array are ignored.</p><p>
18881 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
18882 * allowed in this array.</p><p>
18883 * The typical usage for the blacklist is to **block
18884 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
18885 * these would otherwise be trusted but actually return content from the redirected domain.
18886 * </p><p>
18887 * Finally, **the blacklist overrides the whitelist** and has the final say.
Ed Tanous904063f2017-03-02 16:48:24 -080018888 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070018889 * @return {Array} The currently set blacklist array.
Ed Tanous904063f2017-03-02 16:48:24 -080018890 *
18891 * @description
18892 * Sets/Gets the blacklist of trusted resource URLs.
Ed Tanous4758d5b2017-06-06 15:28:13 -070018893 *
18894 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
18895 * is no blacklist.)
Ed Tanous904063f2017-03-02 16:48:24 -080018896 */
18897
18898 this.resourceUrlBlacklist = function(value) {
18899 if (arguments.length) {
18900 resourceUrlBlacklist = adjustMatchers(value);
18901 }
18902 return resourceUrlBlacklist;
18903 };
18904
18905 this.$get = ['$injector', function($injector) {
18906
18907 var htmlSanitizer = function htmlSanitizer(html) {
18908 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
18909 };
18910
18911 if ($injector.has('$sanitize')) {
18912 htmlSanitizer = $injector.get('$sanitize');
18913 }
18914
18915
18916 function matchUrl(matcher, parsedUrl) {
18917 if (matcher === 'self') {
18918 return urlIsSameOrigin(parsedUrl);
18919 } else {
18920 // definitely a regex. See adjustMatchers()
18921 return !!matcher.exec(parsedUrl.href);
18922 }
18923 }
18924
18925 function isResourceUrlAllowedByPolicy(url) {
18926 var parsedUrl = urlResolve(url.toString());
18927 var i, n, allowed = false;
18928 // Ensure that at least one item from the whitelist allows this url.
18929 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
18930 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
18931 allowed = true;
18932 break;
18933 }
18934 }
18935 if (allowed) {
18936 // Ensure that no item from the blacklist blocked this url.
18937 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
18938 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
18939 allowed = false;
18940 break;
18941 }
18942 }
18943 }
18944 return allowed;
18945 }
18946
18947 function generateHolderType(Base) {
18948 var holderType = function TrustedValueHolderType(trustedValue) {
18949 this.$$unwrapTrustedValue = function() {
18950 return trustedValue;
18951 };
18952 };
18953 if (Base) {
18954 holderType.prototype = new Base();
18955 }
18956 holderType.prototype.valueOf = function sceValueOf() {
18957 return this.$$unwrapTrustedValue();
18958 };
18959 holderType.prototype.toString = function sceToString() {
18960 return this.$$unwrapTrustedValue().toString();
18961 };
18962 return holderType;
18963 }
18964
18965 var trustedValueHolderBase = generateHolderType(),
18966 byType = {};
18967
18968 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
18969 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
18970 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
18971 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
18972 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
18973
18974 /**
18975 * @ngdoc method
18976 * @name $sceDelegate#trustAs
18977 *
18978 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070018979 * Returns a trusted representation of the parameter for the specified context. This trusted
18980 * object will later on be used as-is, without any security check, by bindings or directives
18981 * that require this security context.
18982 * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass
18983 * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as
18984 * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the
18985 * sanitizer loaded, passing the value itself will render all the HTML that does not pose a
18986 * security risk.
Ed Tanous904063f2017-03-02 16:48:24 -080018987 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070018988 * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those
18989 * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual
18990 * escaping.
18991 *
18992 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
18993 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
18994 *
18995 * @param {*} value The value that should be considered trusted.
18996 * @return {*} A trusted representation of value, that can be used in the given context.
Ed Tanous904063f2017-03-02 16:48:24 -080018997 */
18998 function trustAs(type, trustedValue) {
18999 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
19000 if (!Constructor) {
19001 throw $sceMinErr('icontext',
19002 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
19003 type, trustedValue);
19004 }
19005 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
19006 return trustedValue;
19007 }
19008 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
19009 // mutable objects, we ensure here that the value passed in is actually a string.
19010 if (typeof trustedValue !== 'string') {
19011 throw $sceMinErr('itype',
19012 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
19013 type);
19014 }
19015 return new Constructor(trustedValue);
19016 }
19017
19018 /**
19019 * @ngdoc method
19020 * @name $sceDelegate#valueOf
19021 *
19022 * @description
19023 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
19024 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
19025 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
19026 *
19027 * If the passed parameter is not a value that had been returned by {@link
Ed Tanous4758d5b2017-06-06 15:28:13 -070019028 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is.
Ed Tanous904063f2017-03-02 16:48:24 -080019029 *
19030 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
Ed Tanous4758d5b2017-06-06 15:28:13 -070019031 * call or anything else.
19032 * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
Ed Tanous904063f2017-03-02 16:48:24 -080019033 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
19034 * `value` unchanged.
19035 */
19036 function valueOf(maybeTrusted) {
19037 if (maybeTrusted instanceof trustedValueHolderBase) {
19038 return maybeTrusted.$$unwrapTrustedValue();
19039 } else {
19040 return maybeTrusted;
19041 }
19042 }
19043
19044 /**
19045 * @ngdoc method
19046 * @name $sceDelegate#getTrusted
19047 *
19048 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070019049 * Takes any input, and either returns a value that's safe to use in the specified context, or
19050 * throws an exception.
Ed Tanous904063f2017-03-02 16:48:24 -080019051 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019052 * In practice, there are several cases. When given a string, this function runs checks
19053 * and sanitization to make it safe without prior assumptions. When given the result of a {@link
19054 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call, it returns the originally supplied
19055 * value if that value's context is valid for this call's context. Finally, this function can
19056 * also throw when there is no way to turn `maybeTrusted` in a safe value (e.g., no sanitization
19057 * is available or possible.)
Ed Tanous904063f2017-03-02 16:48:24 -080019058 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019059 * @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
Ed Tanous904063f2017-03-02 16:48:24 -080019060 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
Ed Tanous4758d5b2017-06-06 15:28:13 -070019061 * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.)
19062 * @return {*} A version of the value that's safe to use in the given context, or throws an
19063 * exception if this is impossible.
Ed Tanous904063f2017-03-02 16:48:24 -080019064 */
19065 function getTrusted(type, maybeTrusted) {
19066 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
19067 return maybeTrusted;
19068 }
19069 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
Ed Tanous4758d5b2017-06-06 15:28:13 -070019070 // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return
19071 // as-is.
Ed Tanous904063f2017-03-02 16:48:24 -080019072 if (constructor && maybeTrusted instanceof constructor) {
19073 return maybeTrusted.$$unwrapTrustedValue();
19074 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070019075 // Otherwise, if we get here, then we may either make it safe, or throw an exception. This
19076 // depends on the context: some are sanitizatible (HTML), some use whitelists (RESOURCE_URL),
19077 // some are impossible to do (JS). This step isn't implemented for CSS and URL, as AngularJS
19078 // has no corresponding sinks.
Ed Tanous904063f2017-03-02 16:48:24 -080019079 if (type === SCE_CONTEXTS.RESOURCE_URL) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019080 // RESOURCE_URL uses a whitelist.
Ed Tanous904063f2017-03-02 16:48:24 -080019081 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
19082 return maybeTrusted;
19083 } else {
19084 throw $sceMinErr('insecurl',
19085 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
19086 maybeTrusted.toString());
19087 }
19088 } else if (type === SCE_CONTEXTS.HTML) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019089 // htmlSanitizer throws its own error when no sanitizer is available.
Ed Tanous904063f2017-03-02 16:48:24 -080019090 return htmlSanitizer(maybeTrusted);
19091 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070019092 // Default error when the $sce service has no way to make the input safe.
Ed Tanous904063f2017-03-02 16:48:24 -080019093 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
19094 }
19095
19096 return { trustAs: trustAs,
19097 getTrusted: getTrusted,
19098 valueOf: valueOf };
19099 }];
19100}
19101
19102
19103/**
19104 * @ngdoc provider
19105 * @name $sceProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070019106 * @this
19107 *
Ed Tanous904063f2017-03-02 16:48:24 -080019108 * @description
19109 *
19110 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
19111 * - enable/disable Strict Contextual Escaping (SCE) in a module
19112 * - override the default implementation with a custom delegate
19113 *
19114 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
19115 */
19116
Ed Tanous904063f2017-03-02 16:48:24 -080019117/**
19118 * @ngdoc service
19119 * @name $sce
19120 * @kind function
19121 *
19122 * @description
19123 *
19124 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
19125 *
19126 * # Strict Contextual Escaping
19127 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019128 * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render
19129 * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and
19130 * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
Ed Tanous904063f2017-03-02 16:48:24 -080019131 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019132 * ## Overview
Ed Tanous904063f2017-03-02 16:48:24 -080019133 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019134 * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in
19135 * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically
19136 * run security checks on them (sanitizations, whitelists, depending on context), or throw when it
19137 * cannot guarantee the security of the result. That behavior depends strongly on contexts: HTML
19138 * can be sanitized, but template URLs cannot, for instance.
Ed Tanous904063f2017-03-02 16:48:24 -080019139 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019140 * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML:
19141 * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it
19142 * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and
19143 * render the input as-is, you will need to mark it as trusted for that context before attempting
19144 * to bind it.
19145 *
19146 * As of version 1.2, AngularJS ships with SCE enabled by default.
19147 *
19148 * ## In practice
Ed Tanous904063f2017-03-02 16:48:24 -080019149 *
19150 * Here's an example of a binding in a privileged context:
19151 *
19152 * ```
19153 * <input ng-model="userHtml" aria-label="User input">
19154 * <div ng-bind-html="userHtml"></div>
19155 * ```
19156 *
19157 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
Ed Tanous4758d5b2017-06-06 15:28:13 -070019158 * disabled, this application allows the user to render arbitrary HTML into the DIV, which would
19159 * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog
19160 * articles, etc. via bindings. (HTML is just one example of a context where rendering user
19161 * controlled input creates security vulnerabilities.)
Ed Tanous904063f2017-03-02 16:48:24 -080019162 *
19163 * For the case of HTML, you might use a library, either on the client side, or on the server side,
19164 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
19165 *
19166 * How would you ensure that every place that used these types of bindings was bound to a value that
19167 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
19168 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
19169 * properties/fields and forgot to update the binding to the sanitized value?
19170 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019171 * To be secure by default, AngularJS makes sure bindings go through that sanitization, or
19172 * any similar validation process, unless there's a good reason to trust the given value in this
19173 * context. That trust is formalized with a function call. This means that as a developer, you
19174 * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues,
19175 * you just need to ensure the values you mark as trusted indeed are safe - because they were
19176 * received from your server, sanitized by your library, etc. You can organize your codebase to
19177 * help with this - perhaps allowing only the files in a specific directory to do this.
19178 * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then
19179 * becomes a more manageable task.
Ed Tanous904063f2017-03-02 16:48:24 -080019180 *
19181 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
19182 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
Ed Tanous4758d5b2017-06-06 15:28:13 -070019183 * build the trusted versions of your values.
Ed Tanous904063f2017-03-02 16:48:24 -080019184 *
19185 * ## How does it work?
19186 *
19187 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
Ed Tanous4758d5b2017-06-06 15:28:13 -070019188 * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as
19189 * a way to enforce the required security context in your data sink. Directives use {@link
19190 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs
19191 * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also,
19192 * when binding without directives, AngularJS will understand the context of your bindings
19193 * automatically.
Ed Tanous904063f2017-03-02 16:48:24 -080019194 *
19195 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
19196 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
19197 * simplified):
19198 *
19199 * ```
19200 * var ngBindHtmlDirective = ['$sce', function($sce) {
19201 * return function(scope, element, attr) {
19202 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
19203 * element.html(value || '');
19204 * });
19205 * };
19206 * }];
19207 * ```
19208 *
19209 * ## Impact on loading templates
19210 *
19211 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
19212 * `templateUrl`'s specified by {@link guide/directive directives}.
19213 *
19214 * By default, Angular only loads templates from the same domain and protocol as the application
19215 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
19216 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
19217 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
19218 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
19219 *
19220 * *Please note*:
19221 * The browser's
19222 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
19223 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
19224 * policy apply in addition to this and may further restrict whether the template is successfully
19225 * loaded. This means that without the right CORS policy, loading templates from a different domain
19226 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
19227 * browsers.
19228 *
19229 * ## This feels like too much overhead
19230 *
19231 * It's important to remember that SCE only applies to interpolation expressions.
19232 *
19233 * If your expressions are constant literals, they're automatically trusted and you don't need to
Ed Tanous4758d5b2017-06-06 15:28:13 -070019234 * call `$sce.trustAs` on them (e.g.
19235 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. The `$sceDelegate` will
19236 * also use the `$sanitize` service if it is available when binding untrusted values to
19237 * `$sce.HTML` context. AngularJS provides an implementation in `angular-sanitize.js`, and if you
19238 * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
19239 * your application.
Ed Tanous904063f2017-03-02 16:48:24 -080019240 *
19241 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
19242 * templates in `ng-include` from your application's domain without having to even know about SCE.
19243 * It blocks loading templates from other domains or loading templates over http from an https
19244 * served document. You can change these by setting your own custom {@link
19245 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
19246 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
19247 *
19248 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
19249 * application that's secure and can be audited to verify that with much more ease than bolting
19250 * security onto an application later.
19251 *
19252 * <a name="contexts"></a>
19253 * ## What trusted context types are supported?
19254 *
19255 * | Context | Notes |
19256 * |---------------------|----------------|
Ed Tanous4758d5b2017-06-06 15:28:13 -070019257 * | `$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 $sanitize} service is available (implemented by the {@link ngSanitize ngSanitize} module) this will sanitize the value instead of throwing an error. |
19258 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently, no bindings require this context. Feel free to use it in your own directives. |
19259 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=`, `<img src=`, and some others sanitize their urls and don't constitute an SCE context.) |
19260 * | `$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`, `VIDEO`, `AUDIO`, `SOURCE`, and `TRACK` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does (it's not just the URL that matters, but also what is at the end of it), and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
19261 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently, no bindings require this context. Feel free to use it in your own directives. |
19262 *
19263 *
19264 * Be aware that `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
19265 * through {@link ng.$sce#getTrusted $sce.getTrusted}. There's no CSS-, URL-, or JS-context bindings
19266 * in AngularJS currently, so their corresponding `$sce.trustAs` functions aren't useful yet. This
19267 * might evolve.
Ed Tanous904063f2017-03-02 16:48:24 -080019268 *
19269 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
19270 *
19271 * Each element in these arrays must be one of the following:
19272 *
19273 * - **'self'**
19274 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
19275 * domain** as the application document using the **same protocol**.
19276 * - **String** (except the special value `'self'`)
19277 * - The string is matched against the full *normalized / absolute URL* of the resource
19278 * being tested (substring matches are not good enough.)
19279 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
19280 * match themselves.
19281 * - `*`: matches zero or more occurrences of any character other than one of the following 6
19282 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
19283 * in a whitelist.
19284 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
19285 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
19286 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
19287 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
19288 * http://foo.example.com/templates/**).
19289 * - **RegExp** (*see caveat below*)
19290 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
19291 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
19292 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
19293 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
19294 * small number of cases. A `.` character in the regex used when matching the scheme or a
19295 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
19296 * is highly recommended to use the string patterns and only fall back to regular expressions
19297 * as a last resort.
19298 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
19299 * matched against the **entire** *normalized / absolute URL* of the resource being tested
19300 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
19301 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
19302 * - If you are generating your JavaScript from some other templating engine (not
19303 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
19304 * remember to escape your regular expression (and be aware that you might need more than
19305 * one level of escaping depending on your templating engine and the way you interpolated
19306 * the value.) Do make use of your platform's escaping mechanism as it might be good
19307 * enough before coding your own. E.g. Ruby has
19308 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
19309 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
19310 * Javascript lacks a similar built in function for escaping. Take a look at Google
19311 * Closure library's [goog.string.regExpEscape(s)](
19312 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
19313 *
19314 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
19315 *
19316 * ## Show me an example using SCE.
19317 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019318 * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service">
Ed Tanous904063f2017-03-02 16:48:24 -080019319 * <file name="index.html">
19320 * <div ng-controller="AppController as myCtrl">
19321 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
19322 * <b>User comments</b><br>
19323 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
19324 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
19325 * exploit.
19326 * <div class="well">
19327 * <div ng-repeat="userComment in myCtrl.userComments">
19328 * <b>{{userComment.name}}</b>:
19329 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
19330 * <br>
19331 * </div>
19332 * </div>
19333 * </div>
19334 * </file>
19335 *
19336 * <file name="script.js">
19337 * angular.module('mySceApp', ['ngSanitize'])
19338 * .controller('AppController', ['$http', '$templateCache', '$sce',
Ed Tanous4758d5b2017-06-06 15:28:13 -070019339 * function AppController($http, $templateCache, $sce) {
Ed Tanous904063f2017-03-02 16:48:24 -080019340 * var self = this;
Ed Tanous4758d5b2017-06-06 15:28:13 -070019341 * $http.get('test_data.json', {cache: $templateCache}).then(function(response) {
19342 * self.userComments = response.data;
Ed Tanous904063f2017-03-02 16:48:24 -080019343 * });
19344 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
19345 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
19346 * 'sanitization.&quot;">Hover over this text.</span>');
19347 * }]);
19348 * </file>
19349 *
19350 * <file name="test_data.json">
19351 * [
19352 * { "name": "Alice",
19353 * "htmlComment":
19354 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
19355 * },
19356 * { "name": "Bob",
19357 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
19358 * }
19359 * ]
19360 * </file>
19361 *
19362 * <file name="protractor.js" type="protractor">
19363 * describe('SCE doc demo', function() {
19364 * it('should sanitize untrusted values', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019365 * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML'))
Ed Tanous904063f2017-03-02 16:48:24 -080019366 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
19367 * });
19368 *
19369 * it('should NOT sanitize explicitly trusted values', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019370 * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe(
Ed Tanous904063f2017-03-02 16:48:24 -080019371 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
19372 * 'sanitization.&quot;">Hover over this text.</span>');
19373 * });
19374 * });
19375 * </file>
19376 * </example>
19377 *
19378 *
19379 *
19380 * ## Can I disable SCE completely?
19381 *
19382 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
19383 * for little coding overhead. It will be much harder to take an SCE disabled application and
19384 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
19385 * for cases where you have a lot of existing code that was written before SCE was introduced and
Ed Tanous4758d5b2017-06-06 15:28:13 -070019386 * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if
19387 * you are writing a library, you will cause security bugs applications using it.
Ed Tanous904063f2017-03-02 16:48:24 -080019388 *
19389 * That said, here's how you can completely disable SCE:
19390 *
19391 * ```
19392 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
19393 * // Completely disable SCE. For demonstration purposes only!
Ed Tanous4758d5b2017-06-06 15:28:13 -070019394 * // Do not use in new projects or libraries.
Ed Tanous904063f2017-03-02 16:48:24 -080019395 * $sceProvider.enabled(false);
19396 * });
19397 * ```
19398 *
19399 */
Ed Tanous904063f2017-03-02 16:48:24 -080019400
19401function $SceProvider() {
19402 var enabled = true;
19403
19404 /**
19405 * @ngdoc method
19406 * @name $sceProvider#enabled
19407 * @kind function
19408 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019409 * @param {boolean=} value If provided, then enables/disables SCE application-wide.
19410 * @return {boolean} True if SCE is enabled, false otherwise.
Ed Tanous904063f2017-03-02 16:48:24 -080019411 *
19412 * @description
19413 * Enables/disables SCE and returns the current value.
19414 */
19415 this.enabled = function(value) {
19416 if (arguments.length) {
19417 enabled = !!value;
19418 }
19419 return enabled;
19420 };
19421
19422
19423 /* Design notes on the default implementation for SCE.
19424 *
19425 * The API contract for the SCE delegate
19426 * -------------------------------------
19427 * The SCE delegate object must provide the following 3 methods:
19428 *
19429 * - trustAs(contextEnum, value)
19430 * This method is used to tell the SCE service that the provided value is OK to use in the
19431 * contexts specified by contextEnum. It must return an object that will be accepted by
19432 * getTrusted() for a compatible contextEnum and return this value.
19433 *
19434 * - valueOf(value)
19435 * For values that were not produced by trustAs(), return them as is. For values that were
19436 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
19437 * trustAs is wrapping the given values into some type, this operation unwraps it when given
19438 * such a value.
19439 *
19440 * - getTrusted(contextEnum, value)
19441 * This function should return the a value that is safe to use in the context specified by
19442 * contextEnum or throw and exception otherwise.
19443 *
19444 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
19445 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
19446 * instance, an implementation could maintain a registry of all trusted objects by context. In
19447 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
19448 * return the same object passed in if it was found in the registry under a compatible context or
19449 * throw an exception otherwise. An implementation might only wrap values some of the time based
19450 * on some criteria. getTrusted() might return a value and not throw an exception for special
19451 * constants or objects even if not wrapped. All such implementations fulfill this contract.
19452 *
19453 *
19454 * A note on the inheritance model for SCE contexts
19455 * ------------------------------------------------
19456 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
19457 * is purely an implementation details.
19458 *
19459 * The contract is simply this:
19460 *
19461 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
19462 * will also succeed.
19463 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019464 * Inheritance happens to capture this in a natural way. In some future, we may not use
19465 * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to
19466 * be aware of this detail.
Ed Tanous904063f2017-03-02 16:48:24 -080019467 */
19468
19469 this.$get = ['$parse', '$sceDelegate', function(
19470 $parse, $sceDelegate) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019471 // Support: IE 9-11 only
Ed Tanous904063f2017-03-02 16:48:24 -080019472 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
19473 // the "expression(javascript expression)" syntax which is insecure.
19474 if (enabled && msie < 8) {
19475 throw $sceMinErr('iequirks',
19476 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
19477 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
19478 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
19479 }
19480
19481 var sce = shallowCopy(SCE_CONTEXTS);
19482
19483 /**
19484 * @ngdoc method
19485 * @name $sce#isEnabled
19486 * @kind function
19487 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019488 * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you
19489 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
Ed Tanous904063f2017-03-02 16:48:24 -080019490 *
19491 * @description
19492 * Returns a boolean indicating if SCE is enabled.
19493 */
19494 sce.isEnabled = function() {
19495 return enabled;
19496 };
19497 sce.trustAs = $sceDelegate.trustAs;
19498 sce.getTrusted = $sceDelegate.getTrusted;
19499 sce.valueOf = $sceDelegate.valueOf;
19500
19501 if (!enabled) {
19502 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
19503 sce.valueOf = identity;
19504 }
19505
19506 /**
19507 * @ngdoc method
19508 * @name $sce#parseAs
19509 *
19510 * @description
19511 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
19512 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
19513 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
19514 * *result*)}
19515 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019516 * @param {string} type The SCE context in which this result will be used.
Ed Tanous904063f2017-03-02 16:48:24 -080019517 * @param {string} expression String expression to compile.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019518 * @return {function(context, locals)} A function which represents the compiled expression:
Ed Tanous904063f2017-03-02 16:48:24 -080019519 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019520 * * `context` – `{object}` – an object against which any expressions embedded in the
19521 * strings are evaluated against (typically a scope object).
19522 * * `locals` – `{object=}` – local variables context object, useful for overriding values
19523 * in `context`.
Ed Tanous904063f2017-03-02 16:48:24 -080019524 */
19525 sce.parseAs = function sceParseAs(type, expr) {
19526 var parsed = $parse(expr);
19527 if (parsed.literal && parsed.constant) {
19528 return parsed;
19529 } else {
19530 return $parse(expr, function(value) {
19531 return sce.getTrusted(type, value);
19532 });
19533 }
19534 };
19535
19536 /**
19537 * @ngdoc method
19538 * @name $sce#trustAs
19539 *
19540 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070019541 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a
19542 * wrapped object that represents your value, and the trust you have in its safety for the given
19543 * context. AngularJS can then use that value as-is in bindings of the specified secure context.
19544 * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute
19545 * interpolations. See {@link ng.$sce $sce} for strict contextual escaping.
Ed Tanous904063f2017-03-02 16:48:24 -080019546 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019547 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
19548 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
19549 *
19550 * @param {*} value The value that that should be considered trusted.
19551 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19552 * in the context you specified.
Ed Tanous904063f2017-03-02 16:48:24 -080019553 */
19554
19555 /**
19556 * @ngdoc method
19557 * @name $sce#trustAsHtml
19558 *
19559 * @description
19560 * Shorthand method. `$sce.trustAsHtml(value)` →
19561 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
19562 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019563 * @param {*} value The value to mark as trusted for `$sce.HTML` context.
19564 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19565 * in `$sce.HTML` context (like `ng-bind-html`).
19566 */
19567
19568 /**
19569 * @ngdoc method
19570 * @name $sce#trustAsCss
19571 *
19572 * @description
19573 * Shorthand method. `$sce.trustAsCss(value)` →
19574 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`}
19575 *
19576 * @param {*} value The value to mark as trusted for `$sce.CSS` context.
19577 * @return {*} A wrapped version of value that can be used as a trusted variant
19578 * of your `value` in `$sce.CSS` context. This context is currently unused, so there are
19579 * almost no reasons to use this function so far.
Ed Tanous904063f2017-03-02 16:48:24 -080019580 */
19581
19582 /**
19583 * @ngdoc method
19584 * @name $sce#trustAsUrl
19585 *
19586 * @description
19587 * Shorthand method. `$sce.trustAsUrl(value)` →
19588 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
19589 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019590 * @param {*} value The value to mark as trusted for `$sce.URL` context.
19591 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19592 * in `$sce.URL` context. That context is currently unused, so there are almost no reasons
19593 * to use this function so far.
Ed Tanous904063f2017-03-02 16:48:24 -080019594 */
19595
19596 /**
19597 * @ngdoc method
19598 * @name $sce#trustAsResourceUrl
19599 *
19600 * @description
19601 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
19602 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
19603 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019604 * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context.
19605 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19606 * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute
19607 * bindings, ...)
Ed Tanous904063f2017-03-02 16:48:24 -080019608 */
19609
19610 /**
19611 * @ngdoc method
19612 * @name $sce#trustAsJs
19613 *
19614 * @description
19615 * Shorthand method. `$sce.trustAsJs(value)` →
19616 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
19617 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019618 * @param {*} value The value to mark as trusted for `$sce.JS` context.
19619 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19620 * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to
19621 * use this function so far.
Ed Tanous904063f2017-03-02 16:48:24 -080019622 */
19623
19624 /**
19625 * @ngdoc method
19626 * @name $sce#getTrusted
19627 *
19628 * @description
19629 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
Ed Tanous4758d5b2017-06-06 15:28:13 -070019630 * takes any input, and either returns a value that's safe to use in the specified context,
19631 * or throws an exception. This function is aware of trusted values created by the `trustAs`
19632 * function and its shorthands, and when contexts are appropriate, returns the unwrapped value
19633 * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a
19634 * safe value (e.g., no sanitization is available or possible.)
Ed Tanous904063f2017-03-02 16:48:24 -080019635 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019636 * @param {string} type The context in which this value is to be used.
19637 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs
19638 * `$sce.trustAs`} call, or anything else (which will not be considered trusted.)
19639 * @return {*} A version of the value that's safe to use in the given context, or throws an
19640 * exception if this is impossible.
Ed Tanous904063f2017-03-02 16:48:24 -080019641 */
19642
19643 /**
19644 * @ngdoc method
19645 * @name $sce#getTrustedHtml
19646 *
19647 * @description
19648 * Shorthand method. `$sce.getTrustedHtml(value)` →
19649 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
19650 *
19651 * @param {*} value The value to pass to `$sce.getTrusted`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019652 * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)`
Ed Tanous904063f2017-03-02 16:48:24 -080019653 */
19654
19655 /**
19656 * @ngdoc method
19657 * @name $sce#getTrustedCss
19658 *
19659 * @description
19660 * Shorthand method. `$sce.getTrustedCss(value)` →
19661 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
19662 *
19663 * @param {*} value The value to pass to `$sce.getTrusted`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019664 * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)`
Ed Tanous904063f2017-03-02 16:48:24 -080019665 */
19666
19667 /**
19668 * @ngdoc method
19669 * @name $sce#getTrustedUrl
19670 *
19671 * @description
19672 * Shorthand method. `$sce.getTrustedUrl(value)` →
19673 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
19674 *
19675 * @param {*} value The value to pass to `$sce.getTrusted`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019676 * @return {*} The return value of `$sce.getTrusted($sce.URL, value)`
Ed Tanous904063f2017-03-02 16:48:24 -080019677 */
19678
19679 /**
19680 * @ngdoc method
19681 * @name $sce#getTrustedResourceUrl
19682 *
19683 * @description
19684 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
19685 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
19686 *
19687 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019688 * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
Ed Tanous904063f2017-03-02 16:48:24 -080019689 */
19690
19691 /**
19692 * @ngdoc method
19693 * @name $sce#getTrustedJs
19694 *
19695 * @description
19696 * Shorthand method. `$sce.getTrustedJs(value)` →
19697 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
19698 *
19699 * @param {*} value The value to pass to `$sce.getTrusted`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019700 * @return {*} The return value of `$sce.getTrusted($sce.JS, value)`
Ed Tanous904063f2017-03-02 16:48:24 -080019701 */
19702
19703 /**
19704 * @ngdoc method
19705 * @name $sce#parseAsHtml
19706 *
19707 * @description
19708 * Shorthand method. `$sce.parseAsHtml(expression string)` →
19709 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
19710 *
19711 * @param {string} expression String expression to compile.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019712 * @return {function(context, locals)} A function which represents the compiled expression:
Ed Tanous904063f2017-03-02 16:48:24 -080019713 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019714 * * `context` – `{object}` – an object against which any expressions embedded in the
19715 * strings are evaluated against (typically a scope object).
19716 * * `locals` – `{object=}` – local variables context object, useful for overriding values
19717 * in `context`.
Ed Tanous904063f2017-03-02 16:48:24 -080019718 */
19719
19720 /**
19721 * @ngdoc method
19722 * @name $sce#parseAsCss
19723 *
19724 * @description
19725 * Shorthand method. `$sce.parseAsCss(value)` →
19726 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
19727 *
19728 * @param {string} expression String expression to compile.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019729 * @return {function(context, locals)} A function which represents the compiled expression:
Ed Tanous904063f2017-03-02 16:48:24 -080019730 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019731 * * `context` – `{object}` – an object against which any expressions embedded in the
19732 * strings are evaluated against (typically a scope object).
19733 * * `locals` – `{object=}` – local variables context object, useful for overriding values
19734 * in `context`.
Ed Tanous904063f2017-03-02 16:48:24 -080019735 */
19736
19737 /**
19738 * @ngdoc method
19739 * @name $sce#parseAsUrl
19740 *
19741 * @description
19742 * Shorthand method. `$sce.parseAsUrl(value)` →
19743 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
19744 *
19745 * @param {string} expression String expression to compile.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019746 * @return {function(context, locals)} A function which represents the compiled expression:
Ed Tanous904063f2017-03-02 16:48:24 -080019747 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019748 * * `context` – `{object}` – an object against which any expressions embedded in the
19749 * strings are evaluated against (typically a scope object).
19750 * * `locals` – `{object=}` – local variables context object, useful for overriding values
19751 * in `context`.
Ed Tanous904063f2017-03-02 16:48:24 -080019752 */
19753
19754 /**
19755 * @ngdoc method
19756 * @name $sce#parseAsResourceUrl
19757 *
19758 * @description
19759 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
19760 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
19761 *
19762 * @param {string} expression String expression to compile.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019763 * @return {function(context, locals)} A function which represents the compiled expression:
Ed Tanous904063f2017-03-02 16:48:24 -080019764 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019765 * * `context` – `{object}` – an object against which any expressions embedded in the
19766 * strings are evaluated against (typically a scope object).
19767 * * `locals` – `{object=}` – local variables context object, useful for overriding values
19768 * in `context`.
Ed Tanous904063f2017-03-02 16:48:24 -080019769 */
19770
19771 /**
19772 * @ngdoc method
19773 * @name $sce#parseAsJs
19774 *
19775 * @description
19776 * Shorthand method. `$sce.parseAsJs(value)` →
19777 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
19778 *
19779 * @param {string} expression String expression to compile.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019780 * @return {function(context, locals)} A function which represents the compiled expression:
Ed Tanous904063f2017-03-02 16:48:24 -080019781 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070019782 * * `context` – `{object}` – an object against which any expressions embedded in the
19783 * strings are evaluated against (typically a scope object).
19784 * * `locals` – `{object=}` – local variables context object, useful for overriding values
19785 * in `context`.
Ed Tanous904063f2017-03-02 16:48:24 -080019786 */
19787
19788 // Shorthand delegations.
19789 var parse = sce.parseAs,
19790 getTrusted = sce.getTrusted,
19791 trustAs = sce.trustAs;
19792
19793 forEach(SCE_CONTEXTS, function(enumValue, name) {
19794 var lName = lowercase(name);
Ed Tanous4758d5b2017-06-06 15:28:13 -070019795 sce[snakeToCamel('parse_as_' + lName)] = function(expr) {
Ed Tanous904063f2017-03-02 16:48:24 -080019796 return parse(enumValue, expr);
19797 };
Ed Tanous4758d5b2017-06-06 15:28:13 -070019798 sce[snakeToCamel('get_trusted_' + lName)] = function(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080019799 return getTrusted(enumValue, value);
19800 };
Ed Tanous4758d5b2017-06-06 15:28:13 -070019801 sce[snakeToCamel('trust_as_' + lName)] = function(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080019802 return trustAs(enumValue, value);
19803 };
19804 });
19805
19806 return sce;
19807 }];
19808}
19809
Ed Tanous4758d5b2017-06-06 15:28:13 -070019810/* exported $SnifferProvider */
19811
Ed Tanous904063f2017-03-02 16:48:24 -080019812/**
19813 * !!! This is an undocumented "private" service !!!
19814 *
19815 * @name $sniffer
19816 * @requires $window
19817 * @requires $document
Ed Tanous4758d5b2017-06-06 15:28:13 -070019818 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080019819 *
19820 * @property {boolean} history Does the browser support html5 history api ?
19821 * @property {boolean} transitions Does the browser support CSS transition events ?
19822 * @property {boolean} animations Does the browser support CSS animation events ?
19823 *
19824 * @description
19825 * This is very simple implementation of testing browser's features.
19826 */
19827function $SnifferProvider() {
19828 this.$get = ['$window', '$document', function($window, $document) {
19829 var eventSupport = {},
Ed Tanous4758d5b2017-06-06 15:28:13 -070019830 // Chrome Packaged Apps are not allowed to access `history.pushState`.
19831 // If not sandboxed, they can be detected by the presence of `chrome.app.runtime`
19832 // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by
19833 // the presence of an extension runtime ID and the absence of other Chrome runtime APIs
19834 // (see https://developer.chrome.com/apps/manifest/sandbox).
19835 // (NW.js apps have access to Chrome APIs, but do support `history`.)
19836 isNw = $window.nw && $window.nw.process,
19837 isChromePackagedApp =
19838 !isNw &&
19839 $window.chrome &&
19840 ($window.chrome.app && $window.chrome.app.runtime ||
19841 !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id),
Ed Tanous904063f2017-03-02 16:48:24 -080019842 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState,
19843 android =
19844 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
19845 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
19846 document = $document[0] || {},
Ed Tanous904063f2017-03-02 16:48:24 -080019847 bodyStyle = document.body && document.body.style,
19848 transitions = false,
Ed Tanous4758d5b2017-06-06 15:28:13 -070019849 animations = false;
Ed Tanous904063f2017-03-02 16:48:24 -080019850
19851 if (bodyStyle) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019852 // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x
19853 // Mentioned browsers need a -webkit- prefix for transitions & animations.
19854 transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle);
19855 animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle);
Ed Tanous904063f2017-03-02 16:48:24 -080019856 }
19857
19858
19859 return {
19860 // Android has history.pushState, but it does not update location correctly
19861 // so let's not use the history API at all.
19862 // http://code.google.com/p/android/issues/detail?id=17471
19863 // https://github.com/angular/angular.js/issues/904
19864
19865 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
19866 // so let's not use the history API also
19867 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
Ed Tanous904063f2017-03-02 16:48:24 -080019868 history: !!(hasHistoryPushState && !(android < 4) && !boxee),
Ed Tanous904063f2017-03-02 16:48:24 -080019869 hasEvent: function(event) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070019870 // Support: IE 9-11 only
Ed Tanous904063f2017-03-02 16:48:24 -080019871 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
19872 // it. In particular the event is not fired when backspace or delete key are pressed or
19873 // when cut operation is performed.
19874 // IE10+ implements 'input' event but it erroneously fires under various situations,
19875 // e.g. when placeholder changes, or a form is focused.
Ed Tanous4758d5b2017-06-06 15:28:13 -070019876 if (event === 'input' && msie) return false;
Ed Tanous904063f2017-03-02 16:48:24 -080019877
19878 if (isUndefined(eventSupport[event])) {
19879 var divElm = document.createElement('div');
19880 eventSupport[event] = 'on' + event in divElm;
19881 }
19882
19883 return eventSupport[event];
19884 },
19885 csp: csp(),
Ed Tanous904063f2017-03-02 16:48:24 -080019886 transitions: transitions,
19887 animations: animations,
19888 android: android
19889 };
19890 }];
19891}
19892
19893var $templateRequestMinErr = minErr('$compile');
19894
19895/**
19896 * @ngdoc provider
19897 * @name $templateRequestProvider
Ed Tanous4758d5b2017-06-06 15:28:13 -070019898 * @this
19899 *
Ed Tanous904063f2017-03-02 16:48:24 -080019900 * @description
19901 * Used to configure the options passed to the {@link $http} service when making a template request.
19902 *
19903 * For example, it can be used for specifying the "Accept" header that is sent to the server, when
19904 * requesting a template.
19905 */
19906function $TemplateRequestProvider() {
19907
19908 var httpOptions;
19909
19910 /**
19911 * @ngdoc method
19912 * @name $templateRequestProvider#httpOptions
19913 * @description
19914 * The options to be passed to the {@link $http} service when making the request.
19915 * You can use this to override options such as the "Accept" header for template requests.
19916 *
19917 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the
19918 * options if not overridden here.
19919 *
19920 * @param {string=} value new value for the {@link $http} options.
19921 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter.
19922 */
19923 this.httpOptions = function(val) {
19924 if (val) {
19925 httpOptions = val;
19926 return this;
19927 }
19928 return httpOptions;
19929 };
19930
19931 /**
19932 * @ngdoc service
19933 * @name $templateRequest
19934 *
19935 * @description
19936 * The `$templateRequest` service runs security checks then downloads the provided template using
19937 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
19938 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
19939 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
19940 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
19941 * when `tpl` is of type string and `$templateCache` has the matching entry.
19942 *
19943 * If you want to pass custom options to the `$http` service, such as setting the Accept header you
19944 * can configure this via {@link $templateRequestProvider#httpOptions}.
19945 *
19946 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
19947 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
19948 *
19949 * @return {Promise} a promise for the HTTP response data of the given URL.
19950 *
19951 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
19952 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070019953 this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce',
19954 function($exceptionHandler, $templateCache, $http, $q, $sce) {
Ed Tanous904063f2017-03-02 16:48:24 -080019955
Ed Tanous4758d5b2017-06-06 15:28:13 -070019956 function handleRequestFn(tpl, ignoreRequestError) {
19957 handleRequestFn.totalPendingRequests++;
Ed Tanous904063f2017-03-02 16:48:24 -080019958
Ed Tanous4758d5b2017-06-06 15:28:13 -070019959 // We consider the template cache holds only trusted templates, so
19960 // there's no need to go through whitelisting again for keys that already
19961 // are included in there. This also makes Angular accept any script
19962 // directive, no matter its name. However, we still need to unwrap trusted
19963 // types.
19964 if (!isString(tpl) || isUndefined($templateCache.get(tpl))) {
19965 tpl = $sce.getTrustedResourceUrl(tpl);
Ed Tanous904063f2017-03-02 16:48:24 -080019966 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070019967
19968 var transformResponse = $http.defaults && $http.defaults.transformResponse;
19969
19970 if (isArray(transformResponse)) {
19971 transformResponse = transformResponse.filter(function(transformer) {
19972 return transformer !== defaultHttpResponseTransform;
19973 });
19974 } else if (transformResponse === defaultHttpResponseTransform) {
19975 transformResponse = null;
19976 }
19977
19978 return $http.get(tpl, extend({
19979 cache: $templateCache,
19980 transformResponse: transformResponse
19981 }, httpOptions))
19982 .finally(function() {
19983 handleRequestFn.totalPendingRequests--;
19984 })
19985 .then(function(response) {
19986 $templateCache.put(tpl, response.data);
19987 return response.data;
19988 }, handleError);
19989
19990 function handleError(resp) {
19991 if (!ignoreRequestError) {
19992 resp = $templateRequestMinErr('tpload',
19993 'Failed to load template: {0} (HTTP status: {1} {2})',
19994 tpl, resp.status, resp.statusText);
19995
19996 $exceptionHandler(resp);
19997 }
19998
19999 return $q.reject(resp);
20000 }
Ed Tanous904063f2017-03-02 16:48:24 -080020001 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070020002
20003 handleRequestFn.totalPendingRequests = 0;
20004
20005 return handleRequestFn;
Ed Tanous904063f2017-03-02 16:48:24 -080020006 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070020007 ];
Ed Tanous904063f2017-03-02 16:48:24 -080020008}
20009
Ed Tanous4758d5b2017-06-06 15:28:13 -070020010/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080020011function $$TestabilityProvider() {
20012 this.$get = ['$rootScope', '$browser', '$location',
20013 function($rootScope, $browser, $location) {
20014
20015 /**
20016 * @name $testability
20017 *
20018 * @description
20019 * The private $$testability service provides a collection of methods for use when debugging
20020 * or by automated test and debugging tools.
20021 */
20022 var testability = {};
20023
20024 /**
20025 * @name $$testability#findBindings
20026 *
20027 * @description
20028 * Returns an array of elements that are bound (via ng-bind or {{}})
20029 * to expressions matching the input.
20030 *
20031 * @param {Element} element The element root to search from.
20032 * @param {string} expression The binding expression to match.
20033 * @param {boolean} opt_exactMatch If true, only returns exact matches
20034 * for the expression. Filters and whitespace are ignored.
20035 */
20036 testability.findBindings = function(element, expression, opt_exactMatch) {
20037 var bindings = element.getElementsByClassName('ng-binding');
20038 var matches = [];
20039 forEach(bindings, function(binding) {
20040 var dataBinding = angular.element(binding).data('$binding');
20041 if (dataBinding) {
20042 forEach(dataBinding, function(bindingName) {
20043 if (opt_exactMatch) {
20044 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
20045 if (matcher.test(bindingName)) {
20046 matches.push(binding);
20047 }
20048 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070020049 if (bindingName.indexOf(expression) !== -1) {
Ed Tanous904063f2017-03-02 16:48:24 -080020050 matches.push(binding);
20051 }
20052 }
20053 });
20054 }
20055 });
20056 return matches;
20057 };
20058
20059 /**
20060 * @name $$testability#findModels
20061 *
20062 * @description
20063 * Returns an array of elements that are two-way found via ng-model to
20064 * expressions matching the input.
20065 *
20066 * @param {Element} element The element root to search from.
20067 * @param {string} expression The model expression to match.
20068 * @param {boolean} opt_exactMatch If true, only returns exact matches
20069 * for the expression.
20070 */
20071 testability.findModels = function(element, expression, opt_exactMatch) {
20072 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
20073 for (var p = 0; p < prefixes.length; ++p) {
20074 var attributeEquals = opt_exactMatch ? '=' : '*=';
20075 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
20076 var elements = element.querySelectorAll(selector);
20077 if (elements.length) {
20078 return elements;
20079 }
20080 }
20081 };
20082
20083 /**
20084 * @name $$testability#getLocation
20085 *
20086 * @description
20087 * Shortcut for getting the location in a browser agnostic way. Returns
20088 * the path, search, and hash. (e.g. /path?a=b#hash)
20089 */
20090 testability.getLocation = function() {
20091 return $location.url();
20092 };
20093
20094 /**
20095 * @name $$testability#setLocation
20096 *
20097 * @description
20098 * Shortcut for navigating to a location without doing a full page reload.
20099 *
20100 * @param {string} url The location url (path, search and hash,
20101 * e.g. /path?a=b#hash) to go to.
20102 */
20103 testability.setLocation = function(url) {
20104 if (url !== $location.url()) {
20105 $location.url(url);
20106 $rootScope.$digest();
20107 }
20108 };
20109
20110 /**
20111 * @name $$testability#whenStable
20112 *
20113 * @description
20114 * Calls the callback when $timeout and $http requests are completed.
20115 *
20116 * @param {function} callback
20117 */
20118 testability.whenStable = function(callback) {
20119 $browser.notifyWhenNoOutstandingRequests(callback);
20120 };
20121
20122 return testability;
20123 }];
20124}
20125
Ed Tanous4758d5b2017-06-06 15:28:13 -070020126/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080020127function $TimeoutProvider() {
20128 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
20129 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
20130
20131 var deferreds = {};
20132
20133
20134 /**
20135 * @ngdoc service
20136 * @name $timeout
20137 *
20138 * @description
20139 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
20140 * block and delegates any exceptions to
20141 * {@link ng.$exceptionHandler $exceptionHandler} service.
20142 *
20143 * The return value of calling `$timeout` is a promise, which will be resolved when
20144 * the delay has passed and the timeout function, if provided, is executed.
20145 *
20146 * To cancel a timeout request, call `$timeout.cancel(promise)`.
20147 *
20148 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
20149 * synchronously flush the queue of deferred functions.
20150 *
20151 * If you only want a promise that will be resolved after some specified delay
20152 * then you can call `$timeout` without the `fn` function.
20153 *
20154 * @param {function()=} fn A function, whose execution should be delayed.
20155 * @param {number=} [delay=0] Delay in milliseconds.
20156 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
20157 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
20158 * @param {...*=} Pass additional parameters to the executed function.
20159 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise
20160 * will be resolved with the return value of the `fn` function.
20161 *
20162 */
20163 function timeout(fn, delay, invokeApply) {
20164 if (!isFunction(fn)) {
20165 invokeApply = delay;
20166 delay = fn;
20167 fn = noop;
20168 }
20169
20170 var args = sliceArgs(arguments, 3),
20171 skipApply = (isDefined(invokeApply) && !invokeApply),
20172 deferred = (skipApply ? $$q : $q).defer(),
20173 promise = deferred.promise,
20174 timeoutId;
20175
20176 timeoutId = $browser.defer(function() {
20177 try {
20178 deferred.resolve(fn.apply(null, args));
20179 } catch (e) {
20180 deferred.reject(e);
20181 $exceptionHandler(e);
Ed Tanous4758d5b2017-06-06 15:28:13 -070020182 } finally {
Ed Tanous904063f2017-03-02 16:48:24 -080020183 delete deferreds[promise.$$timeoutId];
20184 }
20185
20186 if (!skipApply) $rootScope.$apply();
20187 }, delay);
20188
20189 promise.$$timeoutId = timeoutId;
20190 deferreds[timeoutId] = deferred;
20191
20192 return promise;
20193 }
20194
20195
20196 /**
20197 * @ngdoc method
20198 * @name $timeout#cancel
20199 *
20200 * @description
20201 * Cancels a task associated with the `promise`. As a result of this, the promise will be
20202 * resolved with a rejection.
20203 *
20204 * @param {Promise=} promise Promise returned by the `$timeout` function.
20205 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
20206 * canceled.
20207 */
20208 timeout.cancel = function(promise) {
20209 if (promise && promise.$$timeoutId in deferreds) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070020210 // Timeout cancels should not report an unhandled promise.
20211 deferreds[promise.$$timeoutId].promise.catch(noop);
Ed Tanous904063f2017-03-02 16:48:24 -080020212 deferreds[promise.$$timeoutId].reject('canceled');
20213 delete deferreds[promise.$$timeoutId];
20214 return $browser.defer.cancel(promise.$$timeoutId);
20215 }
20216 return false;
20217 };
20218
20219 return timeout;
20220 }];
20221}
20222
20223// NOTE: The usage of window and document instead of $window and $document here is
20224// deliberate. This service depends on the specific behavior of anchor nodes created by the
20225// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
20226// cause us to break tests. In addition, when the browser resolves a URL for XHR, it
20227// doesn't know about mocked locations and resolves URLs to the real document - which is
20228// exactly the behavior needed here. There is little value is mocking these out for this
20229// service.
Ed Tanous4758d5b2017-06-06 15:28:13 -070020230var urlParsingNode = window.document.createElement('a');
Ed Tanous904063f2017-03-02 16:48:24 -080020231var originUrl = urlResolve(window.location.href);
20232
20233
20234/**
20235 *
20236 * Implementation Notes for non-IE browsers
20237 * ----------------------------------------
20238 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
20239 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
20240 * URL will be resolved into an absolute URL in the context of the application document.
20241 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
20242 * properties are all populated to reflect the normalized URL. This approach has wide
Ed Tanous4758d5b2017-06-06 15:28:13 -070020243 * compatibility - Safari 1+, Mozilla 1+ etc. See
Ed Tanous904063f2017-03-02 16:48:24 -080020244 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
20245 *
20246 * Implementation Notes for IE
20247 * ---------------------------
20248 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
20249 * browsers. However, the parsed components will not be set if the URL assigned did not specify
20250 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
20251 * work around that by performing the parsing in a 2nd step by taking a previously normalized
20252 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
20253 * properties such as protocol, hostname, port, etc.
20254 *
20255 * References:
20256 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
20257 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
20258 * http://url.spec.whatwg.org/#urlutils
20259 * https://github.com/angular/angular.js/pull/2902
20260 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
20261 *
20262 * @kind function
20263 * @param {string} url The URL to be parsed.
20264 * @description Normalizes and parses a URL.
20265 * @returns {object} Returns the normalized URL as a dictionary.
20266 *
20267 * | member name | Description |
20268 * |---------------|----------------|
20269 * | href | A normalized version of the provided URL if it was not an absolute URL |
20270 * | protocol | The protocol including the trailing colon |
20271 * | host | The host and port (if the port is non-default) of the normalizedUrl |
20272 * | search | The search params, minus the question mark |
20273 * | hash | The hash string, minus the hash symbol
20274 * | hostname | The hostname
20275 * | port | The port, without ":"
20276 * | pathname | The pathname, beginning with "/"
20277 *
20278 */
20279function urlResolve(url) {
20280 var href = url;
20281
Ed Tanous4758d5b2017-06-06 15:28:13 -070020282 // Support: IE 9-11 only
Ed Tanous904063f2017-03-02 16:48:24 -080020283 if (msie) {
20284 // Normalize before parse. Refer Implementation Notes on why this is
20285 // done in two steps on IE.
Ed Tanous4758d5b2017-06-06 15:28:13 -070020286 urlParsingNode.setAttribute('href', href);
Ed Tanous904063f2017-03-02 16:48:24 -080020287 href = urlParsingNode.href;
20288 }
20289
20290 urlParsingNode.setAttribute('href', href);
20291
20292 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
20293 return {
20294 href: urlParsingNode.href,
20295 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
20296 host: urlParsingNode.host,
20297 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
20298 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
20299 hostname: urlParsingNode.hostname,
20300 port: urlParsingNode.port,
20301 pathname: (urlParsingNode.pathname.charAt(0) === '/')
20302 ? urlParsingNode.pathname
20303 : '/' + urlParsingNode.pathname
20304 };
20305}
20306
20307/**
20308 * Parse a request URL and determine whether this is a same-origin request as the application document.
20309 *
20310 * @param {string|object} requestUrl The url of the request as a string that will be resolved
20311 * or a parsed URL object.
20312 * @returns {boolean} Whether the request is for the same origin as the application document.
20313 */
20314function urlIsSameOrigin(requestUrl) {
20315 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
20316 return (parsed.protocol === originUrl.protocol &&
20317 parsed.host === originUrl.host);
20318}
20319
20320/**
20321 * @ngdoc service
20322 * @name $window
Ed Tanous4758d5b2017-06-06 15:28:13 -070020323 * @this
Ed Tanous904063f2017-03-02 16:48:24 -080020324 *
20325 * @description
20326 * A reference to the browser's `window` object. While `window`
20327 * is globally available in JavaScript, it causes testability problems, because
20328 * it is a global variable. In angular we always refer to it through the
20329 * `$window` service, so it may be overridden, removed or mocked for testing.
20330 *
20331 * Expressions, like the one defined for the `ngClick` directive in the example
20332 * below, are evaluated with respect to the current scope. Therefore, there is
20333 * no risk of inadvertently coding in a dependency on a global value in such an
20334 * expression.
20335 *
20336 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070020337 <example module="windowExample" name="window-service">
Ed Tanous904063f2017-03-02 16:48:24 -080020338 <file name="index.html">
20339 <script>
20340 angular.module('windowExample', [])
20341 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
20342 $scope.greeting = 'Hello, World!';
20343 $scope.doGreeting = function(greeting) {
20344 $window.alert(greeting);
20345 };
20346 }]);
20347 </script>
20348 <div ng-controller="ExampleController">
20349 <input type="text" ng-model="greeting" aria-label="greeting" />
20350 <button ng-click="doGreeting(greeting)">ALERT</button>
20351 </div>
20352 </file>
20353 <file name="protractor.js" type="protractor">
20354 it('should display the greeting in the input box', function() {
20355 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
20356 // If we click the button it will block the test runner
20357 // element(':button').click();
20358 });
20359 </file>
20360 </example>
20361 */
20362function $WindowProvider() {
20363 this.$get = valueFn(window);
20364}
20365
20366/**
20367 * @name $$cookieReader
20368 * @requires $document
20369 *
20370 * @description
20371 * This is a private service for reading cookies used by $http and ngCookies
20372 *
20373 * @return {Object} a key/value map of the current cookies
20374 */
20375function $$CookieReader($document) {
20376 var rawDocument = $document[0] || {};
20377 var lastCookies = {};
20378 var lastCookieString = '';
20379
Ed Tanous4758d5b2017-06-06 15:28:13 -070020380 function safeGetCookie(rawDocument) {
20381 try {
20382 return rawDocument.cookie || '';
20383 } catch (e) {
20384 return '';
20385 }
20386 }
20387
Ed Tanous904063f2017-03-02 16:48:24 -080020388 function safeDecodeURIComponent(str) {
20389 try {
20390 return decodeURIComponent(str);
20391 } catch (e) {
20392 return str;
20393 }
20394 }
20395
20396 return function() {
20397 var cookieArray, cookie, i, index, name;
Ed Tanous4758d5b2017-06-06 15:28:13 -070020398 var currentCookieString = safeGetCookie(rawDocument);
Ed Tanous904063f2017-03-02 16:48:24 -080020399
20400 if (currentCookieString !== lastCookieString) {
20401 lastCookieString = currentCookieString;
20402 cookieArray = lastCookieString.split('; ');
20403 lastCookies = {};
20404
20405 for (i = 0; i < cookieArray.length; i++) {
20406 cookie = cookieArray[i];
20407 index = cookie.indexOf('=');
20408 if (index > 0) { //ignore nameless cookies
20409 name = safeDecodeURIComponent(cookie.substring(0, index));
20410 // the first value that is seen for a cookie is the most
20411 // specific one. values for the same cookie name that
20412 // follow are for less specific paths.
20413 if (isUndefined(lastCookies[name])) {
20414 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
20415 }
20416 }
20417 }
20418 }
20419 return lastCookies;
20420 };
20421}
20422
20423$$CookieReader.$inject = ['$document'];
20424
Ed Tanous4758d5b2017-06-06 15:28:13 -070020425/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080020426function $$CookieReaderProvider() {
20427 this.$get = $$CookieReader;
20428}
20429
20430/* global currencyFilter: true,
20431 dateFilter: true,
20432 filterFilter: true,
20433 jsonFilter: true,
20434 limitToFilter: true,
20435 lowercaseFilter: true,
20436 numberFilter: true,
20437 orderByFilter: true,
20438 uppercaseFilter: true,
20439 */
20440
20441/**
20442 * @ngdoc provider
20443 * @name $filterProvider
20444 * @description
20445 *
20446 * Filters are just functions which transform input to an output. However filters need to be
20447 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
20448 * annotated with dependencies and is responsible for creating a filter function.
20449 *
20450 * <div class="alert alert-warning">
20451 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
20452 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
20453 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
20454 * (`myapp_subsection_filterx`).
20455 * </div>
20456 *
20457 * ```js
20458 * // Filter registration
20459 * function MyModule($provide, $filterProvider) {
20460 * // create a service to demonstrate injection (not always needed)
20461 * $provide.value('greet', function(name){
20462 * return 'Hello ' + name + '!';
20463 * });
20464 *
20465 * // register a filter factory which uses the
20466 * // greet service to demonstrate DI.
20467 * $filterProvider.register('greet', function(greet){
20468 * // return the filter function which uses the greet service
20469 * // to generate salutation
20470 * return function(text) {
20471 * // filters need to be forgiving so check input validity
20472 * return text && greet(text) || text;
20473 * };
20474 * });
20475 * }
20476 * ```
20477 *
20478 * The filter function is registered with the `$injector` under the filter name suffix with
20479 * `Filter`.
20480 *
20481 * ```js
20482 * it('should be the same instance', inject(
20483 * function($filterProvider) {
20484 * $filterProvider.register('reverse', function(){
20485 * return ...;
20486 * });
20487 * },
20488 * function($filter, reverseFilter) {
20489 * expect($filter('reverse')).toBe(reverseFilter);
20490 * });
20491 * ```
20492 *
20493 *
20494 * For more information about how angular filters work, and how to create your own filters, see
20495 * {@link guide/filter Filters} in the Angular Developer Guide.
20496 */
20497
20498/**
20499 * @ngdoc service
20500 * @name $filter
20501 * @kind function
20502 * @description
20503 * Filters are used for formatting data displayed to the user.
20504 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070020505 * They can be used in view templates, controllers or services.Angular comes
20506 * with a collection of [built-in filters](api/ng/filter), but it is easy to
20507 * define your own as well.
20508 *
Ed Tanous904063f2017-03-02 16:48:24 -080020509 * The general syntax in templates is as follows:
20510 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070020511 * ```html
20512 * {{ expression [| filter_name[:parameter_value] ... ] }}
20513 * ```
Ed Tanous904063f2017-03-02 16:48:24 -080020514 *
20515 * @param {String} name Name of the filter function to retrieve
20516 * @return {Function} the filter function
20517 * @example
20518 <example name="$filter" module="filterExample">
20519 <file name="index.html">
20520 <div ng-controller="MainCtrl">
20521 <h3>{{ originalText }}</h3>
20522 <h3>{{ filteredText }}</h3>
20523 </div>
20524 </file>
20525
20526 <file name="script.js">
20527 angular.module('filterExample', [])
20528 .controller('MainCtrl', function($scope, $filter) {
20529 $scope.originalText = 'hello';
20530 $scope.filteredText = $filter('uppercase')($scope.originalText);
20531 });
20532 </file>
20533 </example>
20534 */
20535$FilterProvider.$inject = ['$provide'];
Ed Tanous4758d5b2017-06-06 15:28:13 -070020536/** @this */
Ed Tanous904063f2017-03-02 16:48:24 -080020537function $FilterProvider($provide) {
20538 var suffix = 'Filter';
20539
20540 /**
20541 * @ngdoc method
20542 * @name $filterProvider#register
20543 * @param {string|Object} name Name of the filter function, or an object map of filters where
20544 * the keys are the filter names and the values are the filter factories.
20545 *
20546 * <div class="alert alert-warning">
20547 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
20548 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
20549 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
20550 * (`myapp_subsection_filterx`).
20551 * </div>
20552 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
20553 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
20554 * of the registered filter instances.
20555 */
20556 function register(name, factory) {
20557 if (isObject(name)) {
20558 var filters = {};
20559 forEach(name, function(filter, key) {
20560 filters[key] = register(key, filter);
20561 });
20562 return filters;
20563 } else {
20564 return $provide.factory(name + suffix, factory);
20565 }
20566 }
20567 this.register = register;
20568
20569 this.$get = ['$injector', function($injector) {
20570 return function(name) {
20571 return $injector.get(name + suffix);
20572 };
20573 }];
20574
20575 ////////////////////////////////////////
20576
20577 /* global
20578 currencyFilter: false,
20579 dateFilter: false,
20580 filterFilter: false,
20581 jsonFilter: false,
20582 limitToFilter: false,
20583 lowercaseFilter: false,
20584 numberFilter: false,
20585 orderByFilter: false,
Ed Tanous4758d5b2017-06-06 15:28:13 -070020586 uppercaseFilter: false
Ed Tanous904063f2017-03-02 16:48:24 -080020587 */
20588
20589 register('currency', currencyFilter);
20590 register('date', dateFilter);
20591 register('filter', filterFilter);
20592 register('json', jsonFilter);
20593 register('limitTo', limitToFilter);
20594 register('lowercase', lowercaseFilter);
20595 register('number', numberFilter);
20596 register('orderBy', orderByFilter);
20597 register('uppercase', uppercaseFilter);
20598}
20599
20600/**
20601 * @ngdoc filter
20602 * @name filter
20603 * @kind function
20604 *
20605 * @description
20606 * Selects a subset of items from `array` and returns it as a new array.
20607 *
20608 * @param {Array} array The source array.
Ed Tanous4758d5b2017-06-06 15:28:13 -070020609 * <div class="alert alert-info">
20610 * **Note**: If the array contains objects that reference themselves, filtering is not possible.
20611 * </div>
Ed Tanous904063f2017-03-02 16:48:24 -080020612 * @param {string|Object|function()} expression The predicate to be used for selecting items from
20613 * `array`.
20614 *
20615 * Can be one of:
20616 *
20617 * - `string`: The string is used for matching against the contents of the `array`. All strings or
20618 * objects with string properties in `array` that match this string will be returned. This also
20619 * applies to nested object properties.
20620 * The predicate can be negated by prefixing the string with `!`.
20621 *
20622 * - `Object`: A pattern object can be used to filter specific properties on objects contained
20623 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
20624 * which have property `name` containing "M" and property `phone` containing "1". A special
20625 * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
20626 * against any property of the object or its nested object properties. That's equivalent to the
20627 * simple substring match with a `string` as described above. The special property name can be
20628 * overwritten, using the `anyPropertyKey` parameter.
20629 * The predicate can be negated by prefixing the string with `!`.
20630 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
20631 * not containing "M".
20632 *
20633 * Note that a named property will match properties on the same level only, while the special
20634 * `$` property will match properties on the same level or deeper. E.g. an array item like
20635 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
20636 * **will** be matched by `{$: 'John'}`.
20637 *
20638 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
20639 * The function is called for each element of the array, with the element, its index, and
20640 * the entire array itself as arguments.
20641 *
20642 * The final result is an array of those elements that the predicate returned true for.
20643 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070020644 * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in
20645 * determining if values retrieved using `expression` (when it is not a function) should be
20646 * considered a match based on the the expected value (from the filter expression) and actual
20647 * value (from the object in the array).
Ed Tanous904063f2017-03-02 16:48:24 -080020648 *
20649 * Can be one of:
20650 *
20651 * - `function(actual, expected)`:
20652 * The function will be given the object value and the predicate value to compare and
20653 * should return true if both values should be considered equal.
20654 *
20655 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
20656 * This is essentially strict comparison of expected and actual.
20657 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070020658 * - `false`: A short hand for a function which will look for a substring match in a case
20659 * insensitive way. Primitive values are converted to strings. Objects are not compared against
20660 * primitives, unless they have a custom `toString` method (e.g. `Date` objects).
Ed Tanous904063f2017-03-02 16:48:24 -080020661 *
Ed Tanous904063f2017-03-02 16:48:24 -080020662 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070020663 * Defaults to `false`.
20664 *
20665 * @param {string} [anyPropertyKey] The special property name that matches against any property.
Ed Tanous904063f2017-03-02 16:48:24 -080020666 * By default `$`.
20667 *
20668 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070020669 <example name="filter-filter">
Ed Tanous904063f2017-03-02 16:48:24 -080020670 <file name="index.html">
20671 <div ng-init="friends = [{name:'John', phone:'555-1276'},
20672 {name:'Mary', phone:'800-BIG-MARY'},
20673 {name:'Mike', phone:'555-4321'},
20674 {name:'Adam', phone:'555-5678'},
20675 {name:'Julie', phone:'555-8765'},
20676 {name:'Juliette', phone:'555-5678'}]"></div>
20677
20678 <label>Search: <input ng-model="searchText"></label>
20679 <table id="searchTextResults">
20680 <tr><th>Name</th><th>Phone</th></tr>
20681 <tr ng-repeat="friend in friends | filter:searchText">
20682 <td>{{friend.name}}</td>
20683 <td>{{friend.phone}}</td>
20684 </tr>
20685 </table>
20686 <hr>
20687 <label>Any: <input ng-model="search.$"></label> <br>
20688 <label>Name only <input ng-model="search.name"></label><br>
20689 <label>Phone only <input ng-model="search.phone"></label><br>
20690 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
20691 <table id="searchObjResults">
20692 <tr><th>Name</th><th>Phone</th></tr>
20693 <tr ng-repeat="friendObj in friends | filter:search:strict">
20694 <td>{{friendObj.name}}</td>
20695 <td>{{friendObj.phone}}</td>
20696 </tr>
20697 </table>
20698 </file>
20699 <file name="protractor.js" type="protractor">
20700 var expectFriendNames = function(expectedNames, key) {
20701 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
20702 arr.forEach(function(wd, i) {
20703 expect(wd.getText()).toMatch(expectedNames[i]);
20704 });
20705 });
20706 };
20707
20708 it('should search across all fields when filtering with a string', function() {
20709 var searchText = element(by.model('searchText'));
20710 searchText.clear();
20711 searchText.sendKeys('m');
20712 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
20713
20714 searchText.clear();
20715 searchText.sendKeys('76');
20716 expectFriendNames(['John', 'Julie'], 'friend');
20717 });
20718
20719 it('should search in specific fields when filtering with a predicate object', function() {
20720 var searchAny = element(by.model('search.$'));
20721 searchAny.clear();
20722 searchAny.sendKeys('i');
20723 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
20724 });
20725 it('should use a equal comparison when comparator is true', function() {
20726 var searchName = element(by.model('search.name'));
20727 var strict = element(by.model('strict'));
20728 searchName.clear();
20729 searchName.sendKeys('Julie');
20730 strict.click();
20731 expectFriendNames(['Julie'], 'friendObj');
20732 });
20733 </file>
20734 </example>
20735 */
20736
20737function filterFilter() {
20738 return function(array, expression, comparator, anyPropertyKey) {
20739 if (!isArrayLike(array)) {
20740 if (array == null) {
20741 return array;
20742 } else {
20743 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
20744 }
20745 }
20746
20747 anyPropertyKey = anyPropertyKey || '$';
20748 var expressionType = getTypeForFilter(expression);
20749 var predicateFn;
20750 var matchAgainstAnyProp;
20751
20752 switch (expressionType) {
20753 case 'function':
20754 predicateFn = expression;
20755 break;
20756 case 'boolean':
20757 case 'null':
20758 case 'number':
20759 case 'string':
20760 matchAgainstAnyProp = true;
Ed Tanous4758d5b2017-06-06 15:28:13 -070020761 // falls through
Ed Tanous904063f2017-03-02 16:48:24 -080020762 case 'object':
Ed Tanous904063f2017-03-02 16:48:24 -080020763 predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp);
20764 break;
20765 default:
20766 return array;
20767 }
20768
20769 return Array.prototype.filter.call(array, predicateFn);
20770 };
20771}
20772
20773// Helper functions for `filterFilter`
20774function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
20775 var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression);
20776 var predicateFn;
20777
20778 if (comparator === true) {
20779 comparator = equals;
20780 } else if (!isFunction(comparator)) {
20781 comparator = function(actual, expected) {
20782 if (isUndefined(actual)) {
20783 // No substring matching against `undefined`
20784 return false;
20785 }
20786 if ((actual === null) || (expected === null)) {
20787 // No substring matching against `null`; only match against `null`
20788 return actual === expected;
20789 }
20790 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
20791 // Should not compare primitives against objects, unless they have custom `toString` method
20792 return false;
20793 }
20794
20795 actual = lowercase('' + actual);
20796 expected = lowercase('' + expected);
20797 return actual.indexOf(expected) !== -1;
20798 };
20799 }
20800
20801 predicateFn = function(item) {
20802 if (shouldMatchPrimitives && !isObject(item)) {
20803 return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
20804 }
20805 return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
20806 };
20807
20808 return predicateFn;
20809}
20810
20811function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
20812 var actualType = getTypeForFilter(actual);
20813 var expectedType = getTypeForFilter(expected);
20814
20815 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
20816 return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
20817 } else if (isArray(actual)) {
20818 // In case `actual` is an array, consider it a match
20819 // if ANY of it's items matches `expected`
20820 return actual.some(function(item) {
20821 return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
20822 });
20823 }
20824
20825 switch (actualType) {
20826 case 'object':
20827 var key;
20828 if (matchAgainstAnyProp) {
20829 for (key in actual) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070020830 // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined
20831 // See: https://github.com/angular/angular.js/issues/15644
20832 if (key.charAt && (key.charAt(0) !== '$') &&
20833 deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
Ed Tanous904063f2017-03-02 16:48:24 -080020834 return true;
20835 }
20836 }
20837 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false);
20838 } else if (expectedType === 'object') {
20839 for (key in expected) {
20840 var expectedVal = expected[key];
20841 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
20842 continue;
20843 }
20844
20845 var matchAnyProperty = key === anyPropertyKey;
20846 var actualVal = matchAnyProperty ? actual : actual[key];
20847 if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
20848 return false;
20849 }
20850 }
20851 return true;
20852 } else {
20853 return comparator(actual, expected);
20854 }
Ed Tanous904063f2017-03-02 16:48:24 -080020855 case 'function':
20856 return false;
20857 default:
20858 return comparator(actual, expected);
20859 }
20860}
20861
20862// Used for easily differentiating between `null` and actual `object`
20863function getTypeForFilter(val) {
20864 return (val === null) ? 'null' : typeof val;
20865}
20866
20867var MAX_DIGITS = 22;
20868var DECIMAL_SEP = '.';
20869var ZERO_CHAR = '0';
20870
20871/**
20872 * @ngdoc filter
20873 * @name currency
20874 * @kind function
20875 *
20876 * @description
20877 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
20878 * symbol for current locale is used.
20879 *
20880 * @param {number} amount Input to filter.
20881 * @param {string=} symbol Currency symbol or identifier to be displayed.
20882 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
20883 * @returns {string} Formatted number.
20884 *
20885 *
20886 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070020887 <example module="currencyExample" name="currency-filter">
Ed Tanous904063f2017-03-02 16:48:24 -080020888 <file name="index.html">
20889 <script>
20890 angular.module('currencyExample', [])
20891 .controller('ExampleController', ['$scope', function($scope) {
20892 $scope.amount = 1234.56;
20893 }]);
20894 </script>
20895 <div ng-controller="ExampleController">
20896 <input type="number" ng-model="amount" aria-label="amount"> <br>
20897 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
Ed Tanous4758d5b2017-06-06 15:28:13 -070020898 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span><br>
Ed Tanous904063f2017-03-02 16:48:24 -080020899 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
20900 </div>
20901 </file>
20902 <file name="protractor.js" type="protractor">
20903 it('should init with 1234.56', function() {
20904 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
20905 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
20906 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
20907 });
20908 it('should update', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070020909 if (browser.params.browser === 'safari') {
Ed Tanous904063f2017-03-02 16:48:24 -080020910 // Safari does not understand the minus key. See
20911 // https://github.com/angular/protractor/issues/481
20912 return;
20913 }
20914 element(by.model('amount')).clear();
20915 element(by.model('amount')).sendKeys('-1234');
20916 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
20917 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
20918 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
20919 });
20920 </file>
20921 </example>
20922 */
20923currencyFilter.$inject = ['$locale'];
20924function currencyFilter($locale) {
20925 var formats = $locale.NUMBER_FORMATS;
20926 return function(amount, currencySymbol, fractionSize) {
20927 if (isUndefined(currencySymbol)) {
20928 currencySymbol = formats.CURRENCY_SYM;
20929 }
20930
20931 if (isUndefined(fractionSize)) {
20932 fractionSize = formats.PATTERNS[1].maxFrac;
20933 }
20934
20935 // if null or undefined pass it through
20936 return (amount == null)
20937 ? amount
20938 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
20939 replace(/\u00A4/g, currencySymbol);
20940 };
20941}
20942
20943/**
20944 * @ngdoc filter
20945 * @name number
20946 * @kind function
20947 *
20948 * @description
20949 * Formats a number as text.
20950 *
20951 * If the input is null or undefined, it will just be returned.
20952 * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively.
20953 * If the input is not a number an empty string is returned.
20954 *
20955 *
20956 * @param {number|string} number Number to format.
20957 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
20958 * If this is not provided then the fraction size is computed from the current locale's number
20959 * formatting pattern. In the case of the default locale, it will be 3.
20960 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
20961 * locale (e.g., in the en_US locale it will have "." as the decimal separator and
20962 * include "," group separators after each third digit).
20963 *
20964 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070020965 <example module="numberFilterExample" name="number-filter">
Ed Tanous904063f2017-03-02 16:48:24 -080020966 <file name="index.html">
20967 <script>
20968 angular.module('numberFilterExample', [])
20969 .controller('ExampleController', ['$scope', function($scope) {
20970 $scope.val = 1234.56789;
20971 }]);
20972 </script>
20973 <div ng-controller="ExampleController">
20974 <label>Enter number: <input ng-model='val'></label><br>
20975 Default formatting: <span id='number-default'>{{val | number}}</span><br>
20976 No fractions: <span>{{val | number:0}}</span><br>
20977 Negative number: <span>{{-val | number:4}}</span>
20978 </div>
20979 </file>
20980 <file name="protractor.js" type="protractor">
20981 it('should format numbers', function() {
20982 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
20983 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
20984 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
20985 });
20986
20987 it('should update', function() {
20988 element(by.model('val')).clear();
20989 element(by.model('val')).sendKeys('3374.333');
20990 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
20991 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
20992 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
20993 });
20994 </file>
20995 </example>
20996 */
20997numberFilter.$inject = ['$locale'];
20998function numberFilter($locale) {
20999 var formats = $locale.NUMBER_FORMATS;
21000 return function(number, fractionSize) {
21001
21002 // if null or undefined pass it through
21003 return (number == null)
21004 ? number
21005 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
21006 fractionSize);
21007 };
21008}
21009
21010/**
21011 * Parse a number (as a string) into three components that can be used
21012 * for formatting the number.
21013 *
21014 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
21015 *
21016 * @param {string} numStr The number to parse
21017 * @return {object} An object describing this number, containing the following keys:
21018 * - d : an array of digits containing leading zeros as necessary
21019 * - i : the number of the digits in `d` that are to the left of the decimal point
21020 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
21021 *
21022 */
21023function parse(numStr) {
21024 var exponent = 0, digits, numberOfIntegerDigits;
21025 var i, j, zeros;
21026
21027 // Decimal point?
21028 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
21029 numStr = numStr.replace(DECIMAL_SEP, '');
21030 }
21031
21032 // Exponential form?
21033 if ((i = numStr.search(/e/i)) > 0) {
21034 // Work out the exponent.
21035 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i;
21036 numberOfIntegerDigits += +numStr.slice(i + 1);
21037 numStr = numStr.substring(0, i);
21038 } else if (numberOfIntegerDigits < 0) {
21039 // There was no decimal point or exponent so it is an integer.
21040 numberOfIntegerDigits = numStr.length;
21041 }
21042
21043 // Count the number of leading zeros.
Ed Tanous4758d5b2017-06-06 15:28:13 -070021044 for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ }
Ed Tanous904063f2017-03-02 16:48:24 -080021045
Ed Tanous4758d5b2017-06-06 15:28:13 -070021046 if (i === (zeros = numStr.length)) {
Ed Tanous904063f2017-03-02 16:48:24 -080021047 // The digits are all zero.
21048 digits = [0];
21049 numberOfIntegerDigits = 1;
21050 } else {
21051 // Count the number of trailing zeros
21052 zeros--;
Ed Tanous4758d5b2017-06-06 15:28:13 -070021053 while (numStr.charAt(zeros) === ZERO_CHAR) zeros--;
Ed Tanous904063f2017-03-02 16:48:24 -080021054
21055 // Trailing zeros are insignificant so ignore them
21056 numberOfIntegerDigits -= i;
21057 digits = [];
21058 // Convert string to array of digits without leading/trailing zeros.
21059 for (j = 0; i <= zeros; i++, j++) {
21060 digits[j] = +numStr.charAt(i);
21061 }
21062 }
21063
21064 // If the number overflows the maximum allowed digits then use an exponent.
21065 if (numberOfIntegerDigits > MAX_DIGITS) {
21066 digits = digits.splice(0, MAX_DIGITS - 1);
21067 exponent = numberOfIntegerDigits - 1;
21068 numberOfIntegerDigits = 1;
21069 }
21070
21071 return { d: digits, e: exponent, i: numberOfIntegerDigits };
21072}
21073
21074/**
21075 * Round the parsed number to the specified number of decimal places
21076 * This function changed the parsedNumber in-place
21077 */
21078function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) {
21079 var digits = parsedNumber.d;
21080 var fractionLen = digits.length - parsedNumber.i;
21081
21082 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number
21083 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize;
21084
21085 // The index of the digit to where rounding is to occur
21086 var roundAt = fractionSize + parsedNumber.i;
21087 var digit = digits[roundAt];
21088
21089 if (roundAt > 0) {
21090 // Drop fractional digits beyond `roundAt`
21091 digits.splice(Math.max(parsedNumber.i, roundAt));
21092
21093 // Set non-fractional digits beyond `roundAt` to 0
21094 for (var j = roundAt; j < digits.length; j++) {
21095 digits[j] = 0;
21096 }
21097 } else {
21098 // We rounded to zero so reset the parsedNumber
21099 fractionLen = Math.max(0, fractionLen);
21100 parsedNumber.i = 1;
21101 digits.length = Math.max(1, roundAt = fractionSize + 1);
21102 digits[0] = 0;
21103 for (var i = 1; i < roundAt; i++) digits[i] = 0;
21104 }
21105
21106 if (digit >= 5) {
21107 if (roundAt - 1 < 0) {
21108 for (var k = 0; k > roundAt; k--) {
21109 digits.unshift(0);
21110 parsedNumber.i++;
21111 }
21112 digits.unshift(1);
21113 parsedNumber.i++;
21114 } else {
21115 digits[roundAt - 1]++;
21116 }
21117 }
21118
21119 // Pad out with zeros to get the required fraction length
21120 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);
21121
21122
21123 // Do any carrying, e.g. a digit was rounded up to 10
21124 var carry = digits.reduceRight(function(carry, d, i, digits) {
21125 d = d + carry;
21126 digits[i] = d % 10;
21127 return Math.floor(d / 10);
21128 }, 0);
21129 if (carry) {
21130 digits.unshift(carry);
21131 parsedNumber.i++;
21132 }
21133}
21134
21135/**
21136 * Format a number into a string
21137 * @param {number} number The number to format
21138 * @param {{
21139 * minFrac, // the minimum number of digits required in the fraction part of the number
21140 * maxFrac, // the maximum number of digits required in the fraction part of the number
21141 * gSize, // number of digits in each group of separated digits
21142 * lgSize, // number of digits in the last group of digits before the decimal separator
21143 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`))
21144 * posPre, // the string to go in front of a positive number
21145 * negSuf, // the string to go after a negative number (e.g. `)`)
21146 * posSuf // the string to go after a positive number
21147 * }} pattern
21148 * @param {string} groupSep The string to separate groups of number (e.g. `,`)
21149 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`)
21150 * @param {[type]} fractionSize The size of the fractional part of the number
21151 * @return {string} The number formatted as a string
21152 */
21153function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
21154
21155 if (!(isString(number) || isNumber(number)) || isNaN(number)) return '';
21156
21157 var isInfinity = !isFinite(number);
21158 var isZero = false;
21159 var numStr = Math.abs(number) + '',
21160 formattedText = '',
21161 parsedNumber;
21162
21163 if (isInfinity) {
21164 formattedText = '\u221e';
21165 } else {
21166 parsedNumber = parse(numStr);
21167
21168 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac);
21169
21170 var digits = parsedNumber.d;
21171 var integerLen = parsedNumber.i;
21172 var exponent = parsedNumber.e;
21173 var decimals = [];
21174 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true);
21175
21176 // pad zeros for small numbers
21177 while (integerLen < 0) {
21178 digits.unshift(0);
21179 integerLen++;
21180 }
21181
21182 // extract decimals digits
21183 if (integerLen > 0) {
21184 decimals = digits.splice(integerLen, digits.length);
21185 } else {
21186 decimals = digits;
21187 digits = [0];
21188 }
21189
21190 // format the integer digits with grouping separators
21191 var groups = [];
21192 if (digits.length >= pattern.lgSize) {
21193 groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
21194 }
21195 while (digits.length > pattern.gSize) {
21196 groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
21197 }
21198 if (digits.length) {
21199 groups.unshift(digits.join(''));
21200 }
21201 formattedText = groups.join(groupSep);
21202
21203 // append the decimal digits
21204 if (decimals.length) {
21205 formattedText += decimalSep + decimals.join('');
21206 }
21207
21208 if (exponent) {
21209 formattedText += 'e+' + exponent;
21210 }
21211 }
21212 if (number < 0 && !isZero) {
21213 return pattern.negPre + formattedText + pattern.negSuf;
21214 } else {
21215 return pattern.posPre + formattedText + pattern.posSuf;
21216 }
21217}
21218
21219function padNumber(num, digits, trim, negWrap) {
21220 var neg = '';
21221 if (num < 0 || (negWrap && num <= 0)) {
21222 if (negWrap) {
21223 num = -num + 1;
21224 } else {
21225 num = -num;
21226 neg = '-';
21227 }
21228 }
21229 num = '' + num;
21230 while (num.length < digits) num = ZERO_CHAR + num;
21231 if (trim) {
21232 num = num.substr(num.length - digits);
21233 }
21234 return neg + num;
21235}
21236
21237
21238function dateGetter(name, size, offset, trim, negWrap) {
21239 offset = offset || 0;
21240 return function(date) {
21241 var value = date['get' + name]();
21242 if (offset > 0 || value > -offset) {
21243 value += offset;
21244 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070021245 if (value === 0 && offset === -12) value = 12;
Ed Tanous904063f2017-03-02 16:48:24 -080021246 return padNumber(value, size, trim, negWrap);
21247 };
21248}
21249
21250function dateStrGetter(name, shortForm, standAlone) {
21251 return function(date, formats) {
21252 var value = date['get' + name]();
21253 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : '');
21254 var get = uppercase(propPrefix + name);
21255
21256 return formats[get][value];
21257 };
21258}
21259
21260function timeZoneGetter(date, formats, offset) {
21261 var zone = -1 * offset;
Ed Tanous4758d5b2017-06-06 15:28:13 -070021262 var paddedZone = (zone >= 0) ? '+' : '';
Ed Tanous904063f2017-03-02 16:48:24 -080021263
21264 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
21265 padNumber(Math.abs(zone % 60), 2);
21266
21267 return paddedZone;
21268}
21269
21270function getFirstThursdayOfYear(year) {
21271 // 0 = index of January
21272 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
21273 // 4 = index of Thursday (+1 to account for 1st = 5)
21274 // 11 = index of *next* Thursday (+1 account for 1st = 12)
21275 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
21276}
21277
21278function getThursdayThisWeek(datetime) {
21279 return new Date(datetime.getFullYear(), datetime.getMonth(),
21280 // 4 = index of Thursday
21281 datetime.getDate() + (4 - datetime.getDay()));
21282}
21283
21284function weekGetter(size) {
21285 return function(date) {
21286 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
21287 thisThurs = getThursdayThisWeek(date);
21288
21289 var diff = +thisThurs - +firstThurs,
21290 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
21291
21292 return padNumber(result, size);
21293 };
21294}
21295
21296function ampmGetter(date, formats) {
21297 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
21298}
21299
21300function eraGetter(date, formats) {
21301 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
21302}
21303
21304function longEraGetter(date, formats) {
21305 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
21306}
21307
21308var DATE_FORMATS = {
21309 yyyy: dateGetter('FullYear', 4, 0, false, true),
21310 yy: dateGetter('FullYear', 2, 0, true, true),
21311 y: dateGetter('FullYear', 1, 0, false, true),
21312 MMMM: dateStrGetter('Month'),
21313 MMM: dateStrGetter('Month', true),
21314 MM: dateGetter('Month', 2, 1),
21315 M: dateGetter('Month', 1, 1),
21316 LLLL: dateStrGetter('Month', false, true),
21317 dd: dateGetter('Date', 2),
21318 d: dateGetter('Date', 1),
21319 HH: dateGetter('Hours', 2),
21320 H: dateGetter('Hours', 1),
21321 hh: dateGetter('Hours', 2, -12),
21322 h: dateGetter('Hours', 1, -12),
21323 mm: dateGetter('Minutes', 2),
21324 m: dateGetter('Minutes', 1),
21325 ss: dateGetter('Seconds', 2),
21326 s: dateGetter('Seconds', 1),
21327 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
21328 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
21329 sss: dateGetter('Milliseconds', 3),
21330 EEEE: dateStrGetter('Day'),
21331 EEE: dateStrGetter('Day', true),
21332 a: ampmGetter,
21333 Z: timeZoneGetter,
21334 ww: weekGetter(2),
21335 w: weekGetter(1),
21336 G: eraGetter,
21337 GG: eraGetter,
21338 GGG: eraGetter,
21339 GGGG: longEraGetter
21340};
21341
Ed Tanous4758d5b2017-06-06 15:28:13 -070021342var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,
21343 NUMBER_STRING = /^-?\d+$/;
Ed Tanous904063f2017-03-02 16:48:24 -080021344
21345/**
21346 * @ngdoc filter
21347 * @name date
21348 * @kind function
21349 *
21350 * @description
21351 * Formats `date` to a string based on the requested `format`.
21352 *
21353 * `format` string can be composed of the following elements:
21354 *
21355 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
21356 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
21357 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
21358 * * `'MMMM'`: Month in year (January-December)
21359 * * `'MMM'`: Month in year (Jan-Dec)
21360 * * `'MM'`: Month in year, padded (01-12)
21361 * * `'M'`: Month in year (1-12)
21362 * * `'LLLL'`: Stand-alone month in year (January-December)
21363 * * `'dd'`: Day in month, padded (01-31)
21364 * * `'d'`: Day in month (1-31)
21365 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
21366 * * `'EEE'`: Day in Week, (Sun-Sat)
21367 * * `'HH'`: Hour in day, padded (00-23)
21368 * * `'H'`: Hour in day (0-23)
21369 * * `'hh'`: Hour in AM/PM, padded (01-12)
21370 * * `'h'`: Hour in AM/PM, (1-12)
21371 * * `'mm'`: Minute in hour, padded (00-59)
21372 * * `'m'`: Minute in hour (0-59)
21373 * * `'ss'`: Second in minute, padded (00-59)
21374 * * `'s'`: Second in minute (0-59)
21375 * * `'sss'`: Millisecond in second, padded (000-999)
21376 * * `'a'`: AM/PM marker
21377 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
21378 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
21379 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
21380 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
21381 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
21382 *
21383 * `format` string can also be one of the following predefined
21384 * {@link guide/i18n localizable formats}:
21385 *
21386 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
21387 * (e.g. Sep 3, 2010 12:05:08 PM)
21388 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
21389 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
21390 * (e.g. Friday, September 3, 2010)
21391 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
21392 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
21393 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
21394 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
21395 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
21396 *
21397 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
21398 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
21399 * (e.g. `"h 'o''clock'"`).
21400 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070021401 * Any other characters in the `format` string will be output as-is.
21402 *
Ed Tanous904063f2017-03-02 16:48:24 -080021403 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
21404 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
21405 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
21406 * specified in the string input, the time is considered to be in the local timezone.
21407 * @param {string=} format Formatting rules (see Description). If not specified,
21408 * `mediumDate` is used.
21409 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
21410 * continental US time zone abbreviations, but for general use, use a time zone offset, for
21411 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
21412 * If not specified, the timezone of the browser will be used.
21413 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
21414 *
21415 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070021416 <example name="filter-date">
Ed Tanous904063f2017-03-02 16:48:24 -080021417 <file name="index.html">
21418 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
21419 <span>{{1288323623006 | date:'medium'}}</span><br>
21420 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
21421 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
21422 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
21423 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
21424 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
21425 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
21426 </file>
21427 <file name="protractor.js" type="protractor">
21428 it('should format date', function() {
21429 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
21430 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
21431 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
Ed Tanous4758d5b2017-06-06 15:28:13 -070021432 toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/);
Ed Tanous904063f2017-03-02 16:48:24 -080021433 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
21434 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
21435 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
21436 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
21437 });
21438 </file>
21439 </example>
21440 */
21441dateFilter.$inject = ['$locale'];
21442function dateFilter($locale) {
21443
21444
21445 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
21446 // 1 2 3 4 5 6 7 8 9 10 11
21447 function jsonStringToDate(string) {
21448 var match;
Ed Tanous4758d5b2017-06-06 15:28:13 -070021449 if ((match = string.match(R_ISO8601_STR))) {
Ed Tanous904063f2017-03-02 16:48:24 -080021450 var date = new Date(0),
21451 tzHour = 0,
21452 tzMin = 0,
21453 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
21454 timeSetter = match[8] ? date.setUTCHours : date.setHours;
21455
21456 if (match[9]) {
21457 tzHour = toInt(match[9] + match[10]);
21458 tzMin = toInt(match[9] + match[11]);
21459 }
21460 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
21461 var h = toInt(match[4] || 0) - tzHour;
21462 var m = toInt(match[5] || 0) - tzMin;
21463 var s = toInt(match[6] || 0);
21464 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
21465 timeSetter.call(date, h, m, s, ms);
21466 return date;
21467 }
21468 return string;
21469 }
21470
21471
21472 return function(date, format, timezone) {
21473 var text = '',
21474 parts = [],
21475 fn, match;
21476
21477 format = format || 'mediumDate';
21478 format = $locale.DATETIME_FORMATS[format] || format;
21479 if (isString(date)) {
21480 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
21481 }
21482
21483 if (isNumber(date)) {
21484 date = new Date(date);
21485 }
21486
21487 if (!isDate(date) || !isFinite(date.getTime())) {
21488 return date;
21489 }
21490
21491 while (format) {
21492 match = DATE_FORMATS_SPLIT.exec(format);
21493 if (match) {
21494 parts = concat(parts, match, 1);
21495 format = parts.pop();
21496 } else {
21497 parts.push(format);
21498 format = null;
21499 }
21500 }
21501
21502 var dateTimezoneOffset = date.getTimezoneOffset();
21503 if (timezone) {
21504 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
21505 date = convertTimezoneToLocal(date, timezone, true);
21506 }
21507 forEach(parts, function(value) {
21508 fn = DATE_FORMATS[value];
21509 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
Ed Tanous4758d5b2017-06-06 15:28:13 -070021510 : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
Ed Tanous904063f2017-03-02 16:48:24 -080021511 });
21512
21513 return text;
21514 };
21515}
21516
21517
21518/**
21519 * @ngdoc filter
21520 * @name json
21521 * @kind function
21522 *
21523 * @description
21524 * Allows you to convert a JavaScript object into JSON string.
21525 *
21526 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
21527 * the binding is automatically converted to JSON.
21528 *
21529 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
21530 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
21531 * @returns {string} JSON string.
21532 *
21533 *
21534 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070021535 <example name="filter-json">
Ed Tanous904063f2017-03-02 16:48:24 -080021536 <file name="index.html">
21537 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
21538 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
21539 </file>
21540 <file name="protractor.js" type="protractor">
21541 it('should jsonify filtered objects', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070021542 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/);
21543 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/);
Ed Tanous904063f2017-03-02 16:48:24 -080021544 });
21545 </file>
21546 </example>
21547 *
21548 */
21549function jsonFilter() {
21550 return function(object, spacing) {
21551 if (isUndefined(spacing)) {
21552 spacing = 2;
21553 }
21554 return toJson(object, spacing);
21555 };
21556}
21557
21558
21559/**
21560 * @ngdoc filter
21561 * @name lowercase
21562 * @kind function
21563 * @description
21564 * Converts string to lowercase.
21565 * @see angular.lowercase
21566 */
21567var lowercaseFilter = valueFn(lowercase);
21568
21569
21570/**
21571 * @ngdoc filter
21572 * @name uppercase
21573 * @kind function
21574 * @description
21575 * Converts string to uppercase.
21576 * @see angular.uppercase
21577 */
21578var uppercaseFilter = valueFn(uppercase);
21579
21580/**
21581 * @ngdoc filter
21582 * @name limitTo
21583 * @kind function
21584 *
21585 * @description
21586 * Creates a new array or string containing only a specified number of elements. The elements are
21587 * taken from either the beginning or the end of the source array, string or number, as specified by
21588 * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported
21589 * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input,
21590 * it is converted to a string.
21591 *
21592 * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited.
21593 * @param {string|number} limit - The length of the returned array or string. If the `limit` number
21594 * is positive, `limit` number of items from the beginning of the source array/string are copied.
21595 * If the number is negative, `limit` number of items from the end of the source array/string
21596 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
21597 * the input will be returned unchanged.
21598 * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index,
21599 * `begin` indicates an offset from the end of `input`. Defaults to `0`.
21600 * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had
21601 * less than `limit` elements.
21602 *
21603 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070021604 <example module="limitToExample" name="limit-to-filter">
Ed Tanous904063f2017-03-02 16:48:24 -080021605 <file name="index.html">
21606 <script>
21607 angular.module('limitToExample', [])
21608 .controller('ExampleController', ['$scope', function($scope) {
21609 $scope.numbers = [1,2,3,4,5,6,7,8,9];
21610 $scope.letters = "abcdefghi";
21611 $scope.longNumber = 2345432342;
21612 $scope.numLimit = 3;
21613 $scope.letterLimit = 3;
21614 $scope.longNumberLimit = 3;
21615 }]);
21616 </script>
21617 <div ng-controller="ExampleController">
21618 <label>
21619 Limit {{numbers}} to:
21620 <input type="number" step="1" ng-model="numLimit">
21621 </label>
21622 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
21623 <label>
21624 Limit {{letters}} to:
21625 <input type="number" step="1" ng-model="letterLimit">
21626 </label>
21627 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
21628 <label>
21629 Limit {{longNumber}} to:
21630 <input type="number" step="1" ng-model="longNumberLimit">
21631 </label>
21632 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
21633 </div>
21634 </file>
21635 <file name="protractor.js" type="protractor">
21636 var numLimitInput = element(by.model('numLimit'));
21637 var letterLimitInput = element(by.model('letterLimit'));
21638 var longNumberLimitInput = element(by.model('longNumberLimit'));
21639 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
21640 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
21641 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
21642
21643 it('should limit the number array to first three items', function() {
21644 expect(numLimitInput.getAttribute('value')).toBe('3');
21645 expect(letterLimitInput.getAttribute('value')).toBe('3');
21646 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
21647 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
21648 expect(limitedLetters.getText()).toEqual('Output letters: abc');
21649 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
21650 });
21651
21652 // There is a bug in safari and protractor that doesn't like the minus key
21653 // it('should update the output when -3 is entered', function() {
21654 // numLimitInput.clear();
21655 // numLimitInput.sendKeys('-3');
21656 // letterLimitInput.clear();
21657 // letterLimitInput.sendKeys('-3');
21658 // longNumberLimitInput.clear();
21659 // longNumberLimitInput.sendKeys('-3');
21660 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
21661 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
21662 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
21663 // });
21664
21665 it('should not exceed the maximum size of input array', function() {
21666 numLimitInput.clear();
21667 numLimitInput.sendKeys('100');
21668 letterLimitInput.clear();
21669 letterLimitInput.sendKeys('100');
21670 longNumberLimitInput.clear();
21671 longNumberLimitInput.sendKeys('100');
21672 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
21673 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
21674 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
21675 });
21676 </file>
21677 </example>
21678*/
21679function limitToFilter() {
21680 return function(input, limit, begin) {
21681 if (Math.abs(Number(limit)) === Infinity) {
21682 limit = Number(limit);
21683 } else {
21684 limit = toInt(limit);
21685 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070021686 if (isNumberNaN(limit)) return input;
Ed Tanous904063f2017-03-02 16:48:24 -080021687
21688 if (isNumber(input)) input = input.toString();
21689 if (!isArrayLike(input)) return input;
21690
21691 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
21692 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
21693
21694 if (limit >= 0) {
21695 return sliceFn(input, begin, begin + limit);
21696 } else {
21697 if (begin === 0) {
21698 return sliceFn(input, limit, input.length);
21699 } else {
21700 return sliceFn(input, Math.max(0, begin + limit), begin);
21701 }
21702 }
21703 };
21704}
21705
21706function sliceFn(input, begin, end) {
21707 if (isString(input)) return input.slice(begin, end);
21708
21709 return slice.call(input, begin, end);
21710}
21711
21712/**
21713 * @ngdoc filter
21714 * @name orderBy
21715 * @kind function
21716 *
21717 * @description
21718 * Returns an array containing the items from the specified `collection`, ordered by a `comparator`
21719 * function based on the values computed using the `expression` predicate.
21720 *
21721 * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in
21722 * `[{id: 'bar'}, {id: 'foo'}]`.
21723 *
21724 * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray,
21725 * String, etc).
21726 *
21727 * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker
Ed Tanous4758d5b2017-06-06 15:28:13 -070021728 * for the preceding one. The `expression` is evaluated against each item and the output is used
Ed Tanous904063f2017-03-02 16:48:24 -080021729 * for comparing with other items.
21730 *
21731 * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in
21732 * ascending order.
21733 *
21734 * The comparison is done using the `comparator` function. If none is specified, a default, built-in
21735 * comparator is used (see below for details - in a nutshell, it compares numbers numerically and
21736 * strings alphabetically).
21737 *
21738 * ### Under the hood
21739 *
21740 * Ordering the specified `collection` happens in two phases:
21741 *
21742 * 1. All items are passed through the predicate (or predicates), and the returned values are saved
21743 * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed
21744 * through a predicate that extracts the value of the `label` property, would be transformed to:
21745 * ```
21746 * {
21747 * value: 'foo',
21748 * type: 'string',
21749 * index: ...
21750 * }
21751 * ```
21752 * 2. The comparator function is used to sort the items, based on the derived values, types and
21753 * indices.
21754 *
21755 * If you use a custom comparator, it will be called with pairs of objects of the form
21756 * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal
21757 * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the
21758 * second, or `1` otherwise.
21759 *
21760 * In order to ensure that the sorting will be deterministic across platforms, if none of the
21761 * specified predicates can distinguish between two items, `orderBy` will automatically introduce a
21762 * dummy predicate that returns the item's index as `value`.
21763 * (If you are using a custom comparator, make sure it can handle this predicate as well.)
21764 *
21765 * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted
21766 * value for an item, `orderBy` will try to convert that object to a primitive value, before passing
21767 * it to the comparator. The following rules govern the conversion:
21768 *
21769 * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be
21770 * used instead.<br />
21771 * (If the object has a `valueOf()` method that returns another object, then the returned object
21772 * will be used in subsequent steps.)
21773 * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that
21774 * returns a primitive, its return value will be used instead.<br />
21775 * (If the object has a `toString()` method that returns another object, then the returned object
21776 * will be used in subsequent steps.)
21777 * 3. No conversion; the object itself is used.
21778 *
21779 * ### The default comparator
21780 *
21781 * The default, built-in comparator should be sufficient for most usecases. In short, it compares
21782 * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
21783 * using their index in the original collection, and sorts values of different types by type.
21784 *
21785 * More specifically, it follows these steps to determine the relative order of items:
21786 *
21787 * 1. If the compared values are of different types, compare the types themselves alphabetically.
21788 * 2. If both values are of type `string`, compare them alphabetically in a case- and
21789 * locale-insensitive way.
21790 * 3. If both values are objects, compare their indices instead.
21791 * 4. Otherwise, return:
21792 * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`).
21793 * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator).
21794 * - `1`, otherwise.
21795 *
21796 * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
21797 * saved as numbers and not strings.
Ed Tanous4758d5b2017-06-06 15:28:13 -070021798 * **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e.
21799 * `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to
21800 * other values.
Ed Tanous904063f2017-03-02 16:48:24 -080021801 *
21802 * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
21803 * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
21804 * predicates) to be used by the comparator to determine the order of elements.
21805 *
21806 * Can be one of:
21807 *
21808 * - `Function`: A getter function. This function will be called with each item as argument and
21809 * the return value will be used for sorting.
21810 * - `string`: An Angular expression. This expression will be evaluated against each item and the
21811 * result will be used for sorting. For example, use `'label'` to sort by a property called
21812 * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label`
21813 * property.<br />
21814 * (The result of a constant expression is interpreted as a property name to be used for
21815 * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a
21816 * property called `special name`.)<br />
21817 * An expression can be optionally prefixed with `+` or `-` to control the sorting direction,
21818 * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided,
21819 * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons.
21820 * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the
21821 * relative order of two items, the next predicate is used as a tie-breaker.
21822 *
21823 * **Note:** If the predicate is missing or empty then it defaults to `'+'`.
21824 *
21825 * @param {boolean=} reverse - If `true`, reverse the sorting order.
21826 * @param {(Function)=} comparator - The comparator function used to determine the relative order of
21827 * value pairs. If omitted, the built-in comparator will be used.
21828 *
21829 * @returns {Array} - The sorted array.
21830 *
21831 *
21832 * @example
21833 * ### Ordering a table with `ngRepeat`
21834 *
21835 * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by
21836 * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means
21837 * it defaults to the built-in comparator.
21838 *
21839 <example name="orderBy-static" module="orderByExample1">
21840 <file name="index.html">
21841 <div ng-controller="ExampleController">
21842 <table class="friends">
21843 <tr>
21844 <th>Name</th>
21845 <th>Phone Number</th>
21846 <th>Age</th>
21847 </tr>
21848 <tr ng-repeat="friend in friends | orderBy:'-age'">
21849 <td>{{friend.name}}</td>
21850 <td>{{friend.phone}}</td>
21851 <td>{{friend.age}}</td>
21852 </tr>
21853 </table>
21854 </div>
21855 </file>
21856 <file name="script.js">
21857 angular.module('orderByExample1', [])
21858 .controller('ExampleController', ['$scope', function($scope) {
21859 $scope.friends = [
21860 {name: 'John', phone: '555-1212', age: 10},
21861 {name: 'Mary', phone: '555-9876', age: 19},
21862 {name: 'Mike', phone: '555-4321', age: 21},
21863 {name: 'Adam', phone: '555-5678', age: 35},
21864 {name: 'Julie', phone: '555-8765', age: 29}
21865 ];
21866 }]);
21867 </file>
21868 <file name="style.css">
21869 .friends {
21870 border-collapse: collapse;
21871 }
21872
21873 .friends th {
21874 border-bottom: 1px solid;
21875 }
21876 .friends td, .friends th {
21877 border-left: 1px solid;
21878 padding: 5px 10px;
21879 }
21880 .friends td:first-child, .friends th:first-child {
21881 border-left: none;
21882 }
21883 </file>
21884 <file name="protractor.js" type="protractor">
21885 // Element locators
21886 var names = element.all(by.repeater('friends').column('friend.name'));
21887
21888 it('should sort friends by age in reverse order', function() {
21889 expect(names.get(0).getText()).toBe('Adam');
21890 expect(names.get(1).getText()).toBe('Julie');
21891 expect(names.get(2).getText()).toBe('Mike');
21892 expect(names.get(3).getText()).toBe('Mary');
21893 expect(names.get(4).getText()).toBe('John');
21894 });
21895 </file>
21896 </example>
21897 * <hr />
21898 *
21899 * @example
21900 * ### Changing parameters dynamically
21901 *
21902 * All parameters can be changed dynamically. The next example shows how you can make the columns of
21903 * a table sortable, by binding the `expression` and `reverse` parameters to scope properties.
21904 *
21905 <example name="orderBy-dynamic" module="orderByExample2">
21906 <file name="index.html">
21907 <div ng-controller="ExampleController">
21908 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
21909 <hr/>
21910 <button ng-click="propertyName = null; reverse = false">Set to unsorted</button>
21911 <hr/>
21912 <table class="friends">
21913 <tr>
21914 <th>
21915 <button ng-click="sortBy('name')">Name</button>
21916 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
21917 </th>
21918 <th>
21919 <button ng-click="sortBy('phone')">Phone Number</button>
21920 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
21921 </th>
21922 <th>
21923 <button ng-click="sortBy('age')">Age</button>
21924 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
21925 </th>
21926 </tr>
21927 <tr ng-repeat="friend in friends | orderBy:propertyName:reverse">
21928 <td>{{friend.name}}</td>
21929 <td>{{friend.phone}}</td>
21930 <td>{{friend.age}}</td>
21931 </tr>
21932 </table>
21933 </div>
21934 </file>
21935 <file name="script.js">
21936 angular.module('orderByExample2', [])
21937 .controller('ExampleController', ['$scope', function($scope) {
21938 var friends = [
21939 {name: 'John', phone: '555-1212', age: 10},
21940 {name: 'Mary', phone: '555-9876', age: 19},
21941 {name: 'Mike', phone: '555-4321', age: 21},
21942 {name: 'Adam', phone: '555-5678', age: 35},
21943 {name: 'Julie', phone: '555-8765', age: 29}
21944 ];
21945
21946 $scope.propertyName = 'age';
21947 $scope.reverse = true;
21948 $scope.friends = friends;
21949
21950 $scope.sortBy = function(propertyName) {
21951 $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
21952 $scope.propertyName = propertyName;
21953 };
21954 }]);
21955 </file>
21956 <file name="style.css">
21957 .friends {
21958 border-collapse: collapse;
21959 }
21960
21961 .friends th {
21962 border-bottom: 1px solid;
21963 }
21964 .friends td, .friends th {
21965 border-left: 1px solid;
21966 padding: 5px 10px;
21967 }
21968 .friends td:first-child, .friends th:first-child {
21969 border-left: none;
21970 }
21971
21972 .sortorder:after {
21973 content: '\25b2'; // BLACK UP-POINTING TRIANGLE
21974 }
21975 .sortorder.reverse:after {
21976 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
21977 }
21978 </file>
21979 <file name="protractor.js" type="protractor">
21980 // Element locators
21981 var unsortButton = element(by.partialButtonText('unsorted'));
21982 var nameHeader = element(by.partialButtonText('Name'));
21983 var phoneHeader = element(by.partialButtonText('Phone'));
21984 var ageHeader = element(by.partialButtonText('Age'));
21985 var firstName = element(by.repeater('friends').column('friend.name').row(0));
21986 var lastName = element(by.repeater('friends').column('friend.name').row(4));
21987
21988 it('should sort friends by some property, when clicking on the column header', function() {
21989 expect(firstName.getText()).toBe('Adam');
21990 expect(lastName.getText()).toBe('John');
21991
21992 phoneHeader.click();
21993 expect(firstName.getText()).toBe('John');
21994 expect(lastName.getText()).toBe('Mary');
21995
21996 nameHeader.click();
21997 expect(firstName.getText()).toBe('Adam');
21998 expect(lastName.getText()).toBe('Mike');
21999
22000 ageHeader.click();
22001 expect(firstName.getText()).toBe('John');
22002 expect(lastName.getText()).toBe('Adam');
22003 });
22004
22005 it('should sort friends in reverse order, when clicking on the same column', function() {
22006 expect(firstName.getText()).toBe('Adam');
22007 expect(lastName.getText()).toBe('John');
22008
22009 ageHeader.click();
22010 expect(firstName.getText()).toBe('John');
22011 expect(lastName.getText()).toBe('Adam');
22012
22013 ageHeader.click();
22014 expect(firstName.getText()).toBe('Adam');
22015 expect(lastName.getText()).toBe('John');
22016 });
22017
22018 it('should restore the original order, when clicking "Set to unsorted"', function() {
22019 expect(firstName.getText()).toBe('Adam');
22020 expect(lastName.getText()).toBe('John');
22021
22022 unsortButton.click();
22023 expect(firstName.getText()).toBe('John');
22024 expect(lastName.getText()).toBe('Julie');
22025 });
22026 </file>
22027 </example>
22028 * <hr />
22029 *
22030 * @example
22031 * ### Using `orderBy` inside a controller
22032 *
22033 * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and
22034 * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory
22035 * and retrieve the `orderBy` filter with `$filter('orderBy')`.)
22036 *
22037 <example name="orderBy-call-manually" module="orderByExample3">
22038 <file name="index.html">
22039 <div ng-controller="ExampleController">
22040 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
22041 <hr/>
22042 <button ng-click="sortBy(null)">Set to unsorted</button>
22043 <hr/>
22044 <table class="friends">
22045 <tr>
22046 <th>
22047 <button ng-click="sortBy('name')">Name</button>
22048 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
22049 </th>
22050 <th>
22051 <button ng-click="sortBy('phone')">Phone Number</button>
22052 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
22053 </th>
22054 <th>
22055 <button ng-click="sortBy('age')">Age</button>
22056 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
22057 </th>
22058 </tr>
22059 <tr ng-repeat="friend in friends">
22060 <td>{{friend.name}}</td>
22061 <td>{{friend.phone}}</td>
22062 <td>{{friend.age}}</td>
22063 </tr>
22064 </table>
22065 </div>
22066 </file>
22067 <file name="script.js">
22068 angular.module('orderByExample3', [])
22069 .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) {
22070 var friends = [
22071 {name: 'John', phone: '555-1212', age: 10},
22072 {name: 'Mary', phone: '555-9876', age: 19},
22073 {name: 'Mike', phone: '555-4321', age: 21},
22074 {name: 'Adam', phone: '555-5678', age: 35},
22075 {name: 'Julie', phone: '555-8765', age: 29}
22076 ];
22077
22078 $scope.propertyName = 'age';
22079 $scope.reverse = true;
22080 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
22081
22082 $scope.sortBy = function(propertyName) {
22083 $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName)
22084 ? !$scope.reverse : false;
22085 $scope.propertyName = propertyName;
22086 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
22087 };
22088 }]);
22089 </file>
22090 <file name="style.css">
22091 .friends {
22092 border-collapse: collapse;
22093 }
22094
22095 .friends th {
22096 border-bottom: 1px solid;
22097 }
22098 .friends td, .friends th {
22099 border-left: 1px solid;
22100 padding: 5px 10px;
22101 }
22102 .friends td:first-child, .friends th:first-child {
22103 border-left: none;
22104 }
22105
22106 .sortorder:after {
22107 content: '\25b2'; // BLACK UP-POINTING TRIANGLE
22108 }
22109 .sortorder.reverse:after {
22110 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
22111 }
22112 </file>
22113 <file name="protractor.js" type="protractor">
22114 // Element locators
22115 var unsortButton = element(by.partialButtonText('unsorted'));
22116 var nameHeader = element(by.partialButtonText('Name'));
22117 var phoneHeader = element(by.partialButtonText('Phone'));
22118 var ageHeader = element(by.partialButtonText('Age'));
22119 var firstName = element(by.repeater('friends').column('friend.name').row(0));
22120 var lastName = element(by.repeater('friends').column('friend.name').row(4));
22121
22122 it('should sort friends by some property, when clicking on the column header', function() {
22123 expect(firstName.getText()).toBe('Adam');
22124 expect(lastName.getText()).toBe('John');
22125
22126 phoneHeader.click();
22127 expect(firstName.getText()).toBe('John');
22128 expect(lastName.getText()).toBe('Mary');
22129
22130 nameHeader.click();
22131 expect(firstName.getText()).toBe('Adam');
22132 expect(lastName.getText()).toBe('Mike');
22133
22134 ageHeader.click();
22135 expect(firstName.getText()).toBe('John');
22136 expect(lastName.getText()).toBe('Adam');
22137 });
22138
22139 it('should sort friends in reverse order, when clicking on the same column', function() {
22140 expect(firstName.getText()).toBe('Adam');
22141 expect(lastName.getText()).toBe('John');
22142
22143 ageHeader.click();
22144 expect(firstName.getText()).toBe('John');
22145 expect(lastName.getText()).toBe('Adam');
22146
22147 ageHeader.click();
22148 expect(firstName.getText()).toBe('Adam');
22149 expect(lastName.getText()).toBe('John');
22150 });
22151
22152 it('should restore the original order, when clicking "Set to unsorted"', function() {
22153 expect(firstName.getText()).toBe('Adam');
22154 expect(lastName.getText()).toBe('John');
22155
22156 unsortButton.click();
22157 expect(firstName.getText()).toBe('John');
22158 expect(lastName.getText()).toBe('Julie');
22159 });
22160 </file>
22161 </example>
22162 * <hr />
22163 *
22164 * @example
22165 * ### Using a custom comparator
22166 *
22167 * If you have very specific requirements about the way items are sorted, you can pass your own
22168 * comparator function. For example, you might need to compare some strings in a locale-sensitive
22169 * way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
22170 * argument - passing `false` retains the default sorting order, i.e. ascending.)
22171 *
22172 <example name="orderBy-custom-comparator" module="orderByExample4">
22173 <file name="index.html">
22174 <div ng-controller="ExampleController">
22175 <div class="friends-container custom-comparator">
22176 <h3>Locale-sensitive Comparator</h3>
22177 <table class="friends">
22178 <tr>
22179 <th>Name</th>
22180 <th>Favorite Letter</th>
22181 </tr>
22182 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator">
22183 <td>{{friend.name}}</td>
22184 <td>{{friend.favoriteLetter}}</td>
22185 </tr>
22186 </table>
22187 </div>
22188 <div class="friends-container default-comparator">
22189 <h3>Default Comparator</h3>
22190 <table class="friends">
22191 <tr>
22192 <th>Name</th>
22193 <th>Favorite Letter</th>
22194 </tr>
22195 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'">
22196 <td>{{friend.name}}</td>
22197 <td>{{friend.favoriteLetter}}</td>
22198 </tr>
22199 </table>
22200 </div>
22201 </div>
22202 </file>
22203 <file name="script.js">
22204 angular.module('orderByExample4', [])
22205 .controller('ExampleController', ['$scope', function($scope) {
22206 $scope.friends = [
22207 {name: 'John', favoriteLetter: 'Ä'},
22208 {name: 'Mary', favoriteLetter: 'Ü'},
22209 {name: 'Mike', favoriteLetter: 'Ö'},
22210 {name: 'Adam', favoriteLetter: 'H'},
22211 {name: 'Julie', favoriteLetter: 'Z'}
22212 ];
22213
22214 $scope.localeSensitiveComparator = function(v1, v2) {
22215 // If we don't get strings, just compare by index
22216 if (v1.type !== 'string' || v2.type !== 'string') {
22217 return (v1.index < v2.index) ? -1 : 1;
22218 }
22219
22220 // Compare strings alphabetically, taking locale into account
22221 return v1.value.localeCompare(v2.value);
22222 };
22223 }]);
22224 </file>
22225 <file name="style.css">
22226 .friends-container {
22227 display: inline-block;
22228 margin: 0 30px;
22229 }
22230
22231 .friends {
22232 border-collapse: collapse;
22233 }
22234
22235 .friends th {
22236 border-bottom: 1px solid;
22237 }
22238 .friends td, .friends th {
22239 border-left: 1px solid;
22240 padding: 5px 10px;
22241 }
22242 .friends td:first-child, .friends th:first-child {
22243 border-left: none;
22244 }
22245 </file>
22246 <file name="protractor.js" type="protractor">
22247 // Element locators
22248 var container = element(by.css('.custom-comparator'));
22249 var names = container.all(by.repeater('friends').column('friend.name'));
22250
22251 it('should sort friends by favorite letter (in correct alphabetical order)', function() {
22252 expect(names.get(0).getText()).toBe('John');
22253 expect(names.get(1).getText()).toBe('Adam');
22254 expect(names.get(2).getText()).toBe('Mike');
22255 expect(names.get(3).getText()).toBe('Mary');
22256 expect(names.get(4).getText()).toBe('Julie');
22257 });
22258 </file>
22259 </example>
22260 *
22261 */
22262orderByFilter.$inject = ['$parse'];
22263function orderByFilter($parse) {
22264 return function(array, sortPredicate, reverseOrder, compareFn) {
22265
22266 if (array == null) return array;
22267 if (!isArrayLike(array)) {
22268 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array);
22269 }
22270
22271 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
22272 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
22273
22274 var predicates = processPredicates(sortPredicate);
22275
22276 var descending = reverseOrder ? -1 : 1;
22277
22278 // Define the `compare()` function. Use a default comparator if none is specified.
22279 var compare = isFunction(compareFn) ? compareFn : defaultCompare;
22280
22281 // The next three lines are a version of a Swartzian Transform idiom from Perl
22282 // (sometimes called the Decorate-Sort-Undecorate idiom)
22283 // See https://en.wikipedia.org/wiki/Schwartzian_transform
22284 var compareValues = Array.prototype.map.call(array, getComparisonObject);
22285 compareValues.sort(doComparison);
22286 array = compareValues.map(function(item) { return item.value; });
22287
22288 return array;
22289
22290 function getComparisonObject(value, index) {
22291 // NOTE: We are adding an extra `tieBreaker` value based on the element's index.
22292 // This will be used to keep the sort stable when none of the input predicates can
22293 // distinguish between two elements.
22294 return {
22295 value: value,
22296 tieBreaker: {value: index, type: 'number', index: index},
22297 predicateValues: predicates.map(function(predicate) {
22298 return getPredicateValue(predicate.get(value), index);
22299 })
22300 };
22301 }
22302
22303 function doComparison(v1, v2) {
22304 for (var i = 0, ii = predicates.length; i < ii; i++) {
22305 var result = compare(v1.predicateValues[i], v2.predicateValues[i]);
22306 if (result) {
22307 return result * predicates[i].descending * descending;
22308 }
22309 }
22310
22311 return compare(v1.tieBreaker, v2.tieBreaker) * descending;
22312 }
22313 };
22314
22315 function processPredicates(sortPredicates) {
22316 return sortPredicates.map(function(predicate) {
22317 var descending = 1, get = identity;
22318
22319 if (isFunction(predicate)) {
22320 get = predicate;
22321 } else if (isString(predicate)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070022322 if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) {
22323 descending = predicate.charAt(0) === '-' ? -1 : 1;
Ed Tanous904063f2017-03-02 16:48:24 -080022324 predicate = predicate.substring(1);
22325 }
22326 if (predicate !== '') {
22327 get = $parse(predicate);
22328 if (get.constant) {
22329 var key = get();
22330 get = function(value) { return value[key]; };
22331 }
22332 }
22333 }
22334 return {get: get, descending: descending};
22335 });
22336 }
22337
22338 function isPrimitive(value) {
22339 switch (typeof value) {
22340 case 'number': /* falls through */
22341 case 'boolean': /* falls through */
22342 case 'string':
22343 return true;
22344 default:
22345 return false;
22346 }
22347 }
22348
22349 function objectValue(value) {
22350 // If `valueOf` is a valid function use that
22351 if (isFunction(value.valueOf)) {
22352 value = value.valueOf();
22353 if (isPrimitive(value)) return value;
22354 }
22355 // If `toString` is a valid function and not the one from `Object.prototype` use that
22356 if (hasCustomToString(value)) {
22357 value = value.toString();
22358 if (isPrimitive(value)) return value;
22359 }
22360
22361 return value;
22362 }
22363
22364 function getPredicateValue(value, index) {
22365 var type = typeof value;
22366 if (value === null) {
22367 type = 'string';
22368 value = 'null';
22369 } else if (type === 'object') {
22370 value = objectValue(value);
22371 }
22372 return {value: value, type: type, index: index};
22373 }
22374
22375 function defaultCompare(v1, v2) {
22376 var result = 0;
22377 var type1 = v1.type;
22378 var type2 = v2.type;
22379
22380 if (type1 === type2) {
22381 var value1 = v1.value;
22382 var value2 = v2.value;
22383
22384 if (type1 === 'string') {
22385 // Compare strings case-insensitively
22386 value1 = value1.toLowerCase();
22387 value2 = value2.toLowerCase();
22388 } else if (type1 === 'object') {
22389 // For basic objects, use the position of the object
22390 // in the collection instead of the value
22391 if (isObject(value1)) value1 = v1.index;
22392 if (isObject(value2)) value2 = v2.index;
22393 }
22394
22395 if (value1 !== value2) {
22396 result = value1 < value2 ? -1 : 1;
22397 }
22398 } else {
22399 result = type1 < type2 ? -1 : 1;
22400 }
22401
22402 return result;
22403 }
22404}
22405
22406function ngDirective(directive) {
22407 if (isFunction(directive)) {
22408 directive = {
22409 link: directive
22410 };
22411 }
22412 directive.restrict = directive.restrict || 'AC';
22413 return valueFn(directive);
22414}
22415
22416/**
22417 * @ngdoc directive
22418 * @name a
22419 * @restrict E
22420 *
22421 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070022422 * Modifies the default behavior of the html a tag so that the default action is prevented when
Ed Tanous904063f2017-03-02 16:48:24 -080022423 * the href attribute is empty.
22424 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070022425 * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive.
Ed Tanous904063f2017-03-02 16:48:24 -080022426 */
22427var htmlAnchorDirective = valueFn({
22428 restrict: 'E',
22429 compile: function(element, attr) {
22430 if (!attr.href && !attr.xlinkHref) {
22431 return function(scope, element) {
22432 // If the linked element is not an anchor tag anymore, do nothing
22433 if (element[0].nodeName.toLowerCase() !== 'a') return;
22434
22435 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
22436 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
22437 'xlink:href' : 'href';
22438 element.on('click', function(event) {
22439 // if we have no href url, then don't navigate anywhere.
22440 if (!element.attr(href)) {
22441 event.preventDefault();
22442 }
22443 });
22444 };
22445 }
22446 }
22447});
22448
22449/**
22450 * @ngdoc directive
22451 * @name ngHref
22452 * @restrict A
22453 * @priority 99
22454 *
22455 * @description
22456 * Using Angular markup like `{{hash}}` in an href attribute will
22457 * make the link go to the wrong URL if the user clicks it before
22458 * Angular has a chance to replace the `{{hash}}` markup with its
22459 * value. Until Angular replaces the markup the link will be broken
22460 * and will most likely return a 404 error. The `ngHref` directive
22461 * solves this problem.
22462 *
22463 * The wrong way to write it:
22464 * ```html
22465 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
22466 * ```
22467 *
22468 * The correct way to write it:
22469 * ```html
22470 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
22471 * ```
22472 *
22473 * @element A
22474 * @param {template} ngHref any string which can contain `{{}}` markup.
22475 *
22476 * @example
22477 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
22478 * in links and their different behaviors:
Ed Tanous4758d5b2017-06-06 15:28:13 -070022479 <example name="ng-href">
Ed Tanous904063f2017-03-02 16:48:24 -080022480 <file name="index.html">
22481 <input ng-model="value" /><br />
22482 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
22483 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
22484 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
22485 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
22486 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
22487 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
22488 </file>
22489 <file name="protractor.js" type="protractor">
22490 it('should execute ng-click but not reload when href without value', function() {
22491 element(by.id('link-1')).click();
22492 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
22493 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
22494 });
22495
22496 it('should execute ng-click but not reload when href empty string', function() {
22497 element(by.id('link-2')).click();
22498 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
22499 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
22500 });
22501
22502 it('should execute ng-click and change url when ng-href specified', function() {
22503 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
22504
22505 element(by.id('link-3')).click();
22506
22507 // At this point, we navigate away from an Angular page, so we need
22508 // to use browser.driver to get the base webdriver.
22509
22510 browser.wait(function() {
22511 return browser.driver.getCurrentUrl().then(function(url) {
22512 return url.match(/\/123$/);
22513 });
22514 }, 5000, 'page should navigate to /123');
22515 });
22516
22517 it('should execute ng-click but not reload when href empty string and name specified', function() {
22518 element(by.id('link-4')).click();
22519 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
22520 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
22521 });
22522
22523 it('should execute ng-click but not reload when no href but name specified', function() {
22524 element(by.id('link-5')).click();
22525 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
22526 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
22527 });
22528
22529 it('should only change url when only ng-href', function() {
22530 element(by.model('value')).clear();
22531 element(by.model('value')).sendKeys('6');
22532 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
22533
22534 element(by.id('link-6')).click();
22535
22536 // At this point, we navigate away from an Angular page, so we need
22537 // to use browser.driver to get the base webdriver.
22538 browser.wait(function() {
22539 return browser.driver.getCurrentUrl().then(function(url) {
22540 return url.match(/\/6$/);
22541 });
22542 }, 5000, 'page should navigate to /6');
22543 });
22544 </file>
22545 </example>
22546 */
22547
22548/**
22549 * @ngdoc directive
22550 * @name ngSrc
22551 * @restrict A
22552 * @priority 99
22553 *
22554 * @description
22555 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
22556 * work right: The browser will fetch from the URL with the literal
22557 * text `{{hash}}` until Angular replaces the expression inside
22558 * `{{hash}}`. The `ngSrc` directive solves this problem.
22559 *
22560 * The buggy way to write it:
22561 * ```html
22562 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
22563 * ```
22564 *
22565 * The correct way to write it:
22566 * ```html
22567 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
22568 * ```
22569 *
22570 * @element IMG
22571 * @param {template} ngSrc any string which can contain `{{}}` markup.
22572 */
22573
22574/**
22575 * @ngdoc directive
22576 * @name ngSrcset
22577 * @restrict A
22578 * @priority 99
22579 *
22580 * @description
22581 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
22582 * work right: The browser will fetch from the URL with the literal
22583 * text `{{hash}}` until Angular replaces the expression inside
22584 * `{{hash}}`. The `ngSrcset` directive solves this problem.
22585 *
22586 * The buggy way to write it:
22587 * ```html
22588 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
22589 * ```
22590 *
22591 * The correct way to write it:
22592 * ```html
22593 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
22594 * ```
22595 *
22596 * @element IMG
22597 * @param {template} ngSrcset any string which can contain `{{}}` markup.
22598 */
22599
22600/**
22601 * @ngdoc directive
22602 * @name ngDisabled
22603 * @restrict A
22604 * @priority 100
22605 *
22606 * @description
22607 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070022608 * This directive sets the `disabled` attribute on the element (typically a form control,
22609 * e.g. `input`, `button`, `select` etc.) if the
Ed Tanous904063f2017-03-02 16:48:24 -080022610 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
22611 *
22612 * A special directive is necessary because we cannot use interpolation inside the `disabled`
22613 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22614 *
22615 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070022616 <example name="ng-disabled">
Ed Tanous904063f2017-03-02 16:48:24 -080022617 <file name="index.html">
22618 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
22619 <button ng-model="button" ng-disabled="checked">Button</button>
22620 </file>
22621 <file name="protractor.js" type="protractor">
22622 it('should toggle button', function() {
22623 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
22624 element(by.model('checked')).click();
22625 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
22626 });
22627 </file>
22628 </example>
22629 *
22630 * @element INPUT
22631 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
22632 * then the `disabled` attribute will be set on the element
22633 */
22634
22635
22636/**
22637 * @ngdoc directive
22638 * @name ngChecked
22639 * @restrict A
22640 * @priority 100
22641 *
22642 * @description
22643 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
22644 *
22645 * Note that this directive should not be used together with {@link ngModel `ngModel`},
22646 * as this can lead to unexpected behavior.
22647 *
22648 * A special directive is necessary because we cannot use interpolation inside the `checked`
22649 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22650 *
22651 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070022652 <example name="ng-checked">
Ed Tanous904063f2017-03-02 16:48:24 -080022653 <file name="index.html">
22654 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
22655 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
22656 </file>
22657 <file name="protractor.js" type="protractor">
22658 it('should check both checkBoxes', function() {
22659 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
22660 element(by.model('master')).click();
22661 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
22662 });
22663 </file>
22664 </example>
22665 *
22666 * @element INPUT
22667 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
22668 * then the `checked` attribute will be set on the element
22669 */
22670
22671
22672/**
22673 * @ngdoc directive
22674 * @name ngReadonly
22675 * @restrict A
22676 * @priority 100
22677 *
22678 * @description
22679 *
22680 * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy.
22681 * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on
22682 * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information.
22683 *
22684 * A special directive is necessary because we cannot use interpolation inside the `readonly`
22685 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22686 *
22687 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070022688 <example name="ng-readonly">
Ed Tanous904063f2017-03-02 16:48:24 -080022689 <file name="index.html">
22690 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
22691 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
22692 </file>
22693 <file name="protractor.js" type="protractor">
22694 it('should toggle readonly attr', function() {
22695 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
22696 element(by.model('checked')).click();
22697 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
22698 });
22699 </file>
22700 </example>
22701 *
22702 * @element INPUT
22703 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
22704 * then special attribute "readonly" will be set on the element
22705 */
22706
22707
22708/**
22709 * @ngdoc directive
22710 * @name ngSelected
22711 * @restrict A
22712 * @priority 100
22713 *
22714 * @description
22715 *
22716 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
22717 *
22718 * A special directive is necessary because we cannot use interpolation inside the `selected`
22719 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22720 *
22721 * <div class="alert alert-warning">
22722 * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only
22723 * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you
22724 * should not use `ngSelected` on the options, as `ngModel` will set the select value and
22725 * selected options.
22726 * </div>
22727 *
22728 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070022729 <example name="ng-selected">
Ed Tanous904063f2017-03-02 16:48:24 -080022730 <file name="index.html">
22731 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
22732 <select aria-label="ngSelected demo">
22733 <option>Hello!</option>
22734 <option id="greet" ng-selected="selected">Greetings!</option>
22735 </select>
22736 </file>
22737 <file name="protractor.js" type="protractor">
22738 it('should select Greetings!', function() {
22739 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
22740 element(by.model('selected')).click();
22741 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
22742 });
22743 </file>
22744 </example>
22745 *
22746 * @element OPTION
22747 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
22748 * then special attribute "selected" will be set on the element
22749 */
22750
22751/**
22752 * @ngdoc directive
22753 * @name ngOpen
22754 * @restrict A
22755 * @priority 100
22756 *
22757 * @description
22758 *
22759 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
22760 *
22761 * A special directive is necessary because we cannot use interpolation inside the `open`
22762 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22763 *
22764 * ## A note about browser compatibility
22765 *
22766 * Edge, Firefox, and Internet Explorer do not support the `details` element, it is
22767 * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead.
22768 *
22769 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070022770 <example name="ng-open">
Ed Tanous904063f2017-03-02 16:48:24 -080022771 <file name="index.html">
22772 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
22773 <details id="details" ng-open="open">
22774 <summary>Show/Hide me</summary>
22775 </details>
22776 </file>
22777 <file name="protractor.js" type="protractor">
22778 it('should toggle open', function() {
22779 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
22780 element(by.model('open')).click();
22781 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
22782 });
22783 </file>
22784 </example>
22785 *
22786 * @element DETAILS
22787 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
22788 * then special attribute "open" will be set on the element
22789 */
22790
22791var ngAttributeAliasDirectives = {};
22792
22793// boolean attrs are evaluated
22794forEach(BOOLEAN_ATTR, function(propName, attrName) {
22795 // binding to multiple is not supported
Ed Tanous4758d5b2017-06-06 15:28:13 -070022796 if (propName === 'multiple') return;
Ed Tanous904063f2017-03-02 16:48:24 -080022797
22798 function defaultLinkFn(scope, element, attr) {
22799 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
22800 attr.$set(attrName, !!value);
22801 });
22802 }
22803
22804 var normalized = directiveNormalize('ng-' + attrName);
22805 var linkFn = defaultLinkFn;
22806
22807 if (propName === 'checked') {
22808 linkFn = function(scope, element, attr) {
22809 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
22810 if (attr.ngModel !== attr[normalized]) {
22811 defaultLinkFn(scope, element, attr);
22812 }
22813 };
22814 }
22815
22816 ngAttributeAliasDirectives[normalized] = function() {
22817 return {
22818 restrict: 'A',
22819 priority: 100,
22820 link: linkFn
22821 };
22822 };
22823});
22824
22825// aliased input attrs are evaluated
22826forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
22827 ngAttributeAliasDirectives[ngAttr] = function() {
22828 return {
22829 priority: 100,
22830 link: function(scope, element, attr) {
22831 //special case ngPattern when a literal regular expression value
22832 //is used as the expression (this way we don't have to watch anything).
Ed Tanous4758d5b2017-06-06 15:28:13 -070022833 if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') {
Ed Tanous904063f2017-03-02 16:48:24 -080022834 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
22835 if (match) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070022836 attr.$set('ngPattern', new RegExp(match[1], match[2]));
Ed Tanous904063f2017-03-02 16:48:24 -080022837 return;
22838 }
22839 }
22840
22841 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
22842 attr.$set(ngAttr, value);
22843 });
22844 }
22845 };
22846 };
22847});
22848
22849// ng-src, ng-srcset, ng-href are interpolated
22850forEach(['src', 'srcset', 'href'], function(attrName) {
22851 var normalized = directiveNormalize('ng-' + attrName);
22852 ngAttributeAliasDirectives[normalized] = function() {
22853 return {
22854 priority: 99, // it needs to run after the attributes are interpolated
22855 link: function(scope, element, attr) {
22856 var propName = attrName,
22857 name = attrName;
22858
22859 if (attrName === 'href' &&
22860 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
22861 name = 'xlinkHref';
22862 attr.$attr[name] = 'xlink:href';
22863 propName = null;
22864 }
22865
22866 attr.$observe(normalized, function(value) {
22867 if (!value) {
22868 if (attrName === 'href') {
22869 attr.$set(name, null);
22870 }
22871 return;
22872 }
22873
22874 attr.$set(name, value);
22875
Ed Tanous4758d5b2017-06-06 15:28:13 -070022876 // Support: IE 9-11 only
22877 // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
Ed Tanous904063f2017-03-02 16:48:24 -080022878 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
22879 // to set the property as well to achieve the desired effect.
Ed Tanous4758d5b2017-06-06 15:28:13 -070022880 // We use attr[attrName] value since $set can sanitize the url.
Ed Tanous904063f2017-03-02 16:48:24 -080022881 if (msie && propName) element.prop(propName, attr[name]);
22882 });
22883 }
22884 };
22885 };
22886});
22887
Ed Tanous4758d5b2017-06-06 15:28:13 -070022888/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS
Ed Tanous904063f2017-03-02 16:48:24 -080022889 */
22890var nullFormCtrl = {
22891 $addControl: noop,
22892 $$renameControl: nullFormRenameControl,
22893 $removeControl: noop,
22894 $setValidity: noop,
22895 $setDirty: noop,
22896 $setPristine: noop,
22897 $setSubmitted: noop
22898},
Ed Tanous4758d5b2017-06-06 15:28:13 -070022899PENDING_CLASS = 'ng-pending',
Ed Tanous904063f2017-03-02 16:48:24 -080022900SUBMITTED_CLASS = 'ng-submitted';
22901
22902function nullFormRenameControl(control, name) {
22903 control.$name = name;
22904}
22905
22906/**
22907 * @ngdoc type
22908 * @name form.FormController
22909 *
22910 * @property {boolean} $pristine True if user has not interacted with the form yet.
22911 * @property {boolean} $dirty True if user has already interacted with the form.
22912 * @property {boolean} $valid True if all of the containing forms and controls are valid.
22913 * @property {boolean} $invalid True if at least one containing control or form is invalid.
22914 * @property {boolean} $pending True if at least one containing control or form is pending.
22915 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
22916 *
22917 * @property {Object} $error Is an object hash, containing references to controls or
22918 * forms with failing validators, where:
22919 *
22920 * - keys are validation tokens (error names),
22921 * - values are arrays of controls or forms that have a failing validator for given error name.
22922 *
22923 * Built-in validation tokens:
22924 *
22925 * - `email`
22926 * - `max`
22927 * - `maxlength`
22928 * - `min`
22929 * - `minlength`
22930 * - `number`
22931 * - `pattern`
22932 * - `required`
22933 * - `url`
22934 * - `date`
22935 * - `datetimelocal`
22936 * - `time`
22937 * - `week`
22938 * - `month`
22939 *
22940 * @description
22941 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
22942 * such as being valid/invalid or dirty/pristine.
22943 *
22944 * Each {@link ng.directive:form form} directive creates an instance
22945 * of `FormController`.
22946 *
22947 */
22948//asks for $scope to fool the BC controller module
22949FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
Ed Tanous4758d5b2017-06-06 15:28:13 -070022950function FormController($element, $attrs, $scope, $animate, $interpolate) {
22951 this.$$controls = [];
Ed Tanous904063f2017-03-02 16:48:24 -080022952
22953 // init state
Ed Tanous4758d5b2017-06-06 15:28:13 -070022954 this.$error = {};
22955 this.$$success = {};
22956 this.$pending = undefined;
22957 this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope);
22958 this.$dirty = false;
22959 this.$pristine = true;
22960 this.$valid = true;
22961 this.$invalid = false;
22962 this.$submitted = false;
22963 this.$$parentForm = nullFormCtrl;
Ed Tanous904063f2017-03-02 16:48:24 -080022964
Ed Tanous4758d5b2017-06-06 15:28:13 -070022965 this.$$element = $element;
22966 this.$$animate = $animate;
22967
22968 setupValidity(this);
22969}
22970
22971FormController.prototype = {
Ed Tanous904063f2017-03-02 16:48:24 -080022972 /**
22973 * @ngdoc method
22974 * @name form.FormController#$rollbackViewValue
22975 *
22976 * @description
22977 * Rollback all form controls pending updates to the `$modelValue`.
22978 *
22979 * Updates may be pending by a debounced event or because the input is waiting for a some future
22980 * event defined in `ng-model-options`. This method is typically needed by the reset button of
22981 * a form that uses `ng-model-options` to pend updates.
22982 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070022983 $rollbackViewValue: function() {
22984 forEach(this.$$controls, function(control) {
Ed Tanous904063f2017-03-02 16:48:24 -080022985 control.$rollbackViewValue();
22986 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070022987 },
Ed Tanous904063f2017-03-02 16:48:24 -080022988
22989 /**
22990 * @ngdoc method
22991 * @name form.FormController#$commitViewValue
22992 *
22993 * @description
22994 * Commit all form controls pending updates to the `$modelValue`.
22995 *
22996 * Updates may be pending by a debounced event or because the input is waiting for a some future
22997 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
22998 * usually handles calling this in response to input events.
22999 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023000 $commitViewValue: function() {
23001 forEach(this.$$controls, function(control) {
Ed Tanous904063f2017-03-02 16:48:24 -080023002 control.$commitViewValue();
23003 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070023004 },
Ed Tanous904063f2017-03-02 16:48:24 -080023005
23006 /**
23007 * @ngdoc method
23008 * @name form.FormController#$addControl
23009 * @param {object} control control object, either a {@link form.FormController} or an
23010 * {@link ngModel.NgModelController}
23011 *
23012 * @description
23013 * Register a control with the form. Input elements using ngModelController do this automatically
23014 * when they are linked.
23015 *
23016 * Note that the current state of the control will not be reflected on the new parent form. This
23017 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
23018 * state.
23019 *
23020 * However, if the method is used programmatically, for example by adding dynamically created controls,
23021 * or controls that have been previously removed without destroying their corresponding DOM element,
23022 * it's the developers responsibility to make sure the current state propagates to the parent form.
23023 *
23024 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
23025 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
23026 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023027 $addControl: function(control) {
Ed Tanous904063f2017-03-02 16:48:24 -080023028 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
23029 // and not added to the scope. Now we throw an error.
23030 assertNotHasOwnProperty(control.$name, 'input');
Ed Tanous4758d5b2017-06-06 15:28:13 -070023031 this.$$controls.push(control);
Ed Tanous904063f2017-03-02 16:48:24 -080023032
23033 if (control.$name) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070023034 this[control.$name] = control;
Ed Tanous904063f2017-03-02 16:48:24 -080023035 }
23036
Ed Tanous4758d5b2017-06-06 15:28:13 -070023037 control.$$parentForm = this;
23038 },
Ed Tanous904063f2017-03-02 16:48:24 -080023039
23040 // Private API: rename a form control
Ed Tanous4758d5b2017-06-06 15:28:13 -070023041 $$renameControl: function(control, newName) {
Ed Tanous904063f2017-03-02 16:48:24 -080023042 var oldName = control.$name;
23043
Ed Tanous4758d5b2017-06-06 15:28:13 -070023044 if (this[oldName] === control) {
23045 delete this[oldName];
Ed Tanous904063f2017-03-02 16:48:24 -080023046 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070023047 this[newName] = control;
Ed Tanous904063f2017-03-02 16:48:24 -080023048 control.$name = newName;
Ed Tanous4758d5b2017-06-06 15:28:13 -070023049 },
Ed Tanous904063f2017-03-02 16:48:24 -080023050
23051 /**
23052 * @ngdoc method
23053 * @name form.FormController#$removeControl
23054 * @param {object} control control object, either a {@link form.FormController} or an
23055 * {@link ngModel.NgModelController}
23056 *
23057 * @description
23058 * Deregister a control from the form.
23059 *
23060 * Input elements using ngModelController do this automatically when they are destroyed.
23061 *
23062 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
23063 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
23064 * different from case to case. For example, removing the only `$dirty` control from a form may or
23065 * may not mean that the form is still `$dirty`.
23066 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023067 $removeControl: function(control) {
23068 if (control.$name && this[control.$name] === control) {
23069 delete this[control.$name];
Ed Tanous904063f2017-03-02 16:48:24 -080023070 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070023071 forEach(this.$pending, function(value, name) {
23072 // eslint-disable-next-line no-invalid-this
23073 this.$setValidity(name, null, control);
23074 }, this);
23075 forEach(this.$error, function(value, name) {
23076 // eslint-disable-next-line no-invalid-this
23077 this.$setValidity(name, null, control);
23078 }, this);
23079 forEach(this.$$success, function(value, name) {
23080 // eslint-disable-next-line no-invalid-this
23081 this.$setValidity(name, null, control);
23082 }, this);
Ed Tanous904063f2017-03-02 16:48:24 -080023083
Ed Tanous4758d5b2017-06-06 15:28:13 -070023084 arrayRemove(this.$$controls, control);
Ed Tanous904063f2017-03-02 16:48:24 -080023085 control.$$parentForm = nullFormCtrl;
Ed Tanous4758d5b2017-06-06 15:28:13 -070023086 },
Ed Tanous904063f2017-03-02 16:48:24 -080023087
23088 /**
23089 * @ngdoc method
23090 * @name form.FormController#$setDirty
23091 *
23092 * @description
23093 * Sets the form to a dirty state.
23094 *
23095 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
23096 * state (ng-dirty class). This method will also propagate to parent forms.
23097 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023098 $setDirty: function() {
23099 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
23100 this.$$animate.addClass(this.$$element, DIRTY_CLASS);
23101 this.$dirty = true;
23102 this.$pristine = false;
23103 this.$$parentForm.$setDirty();
23104 },
Ed Tanous904063f2017-03-02 16:48:24 -080023105
23106 /**
23107 * @ngdoc method
23108 * @name form.FormController#$setPristine
23109 *
23110 * @description
23111 * Sets the form to its pristine state.
23112 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070023113 * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes
23114 * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted`
23115 * state to false.
23116 *
23117 * This method will also propagate to all the controls contained in this form.
Ed Tanous904063f2017-03-02 16:48:24 -080023118 *
23119 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
23120 * saving or resetting it.
23121 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023122 $setPristine: function() {
23123 this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
23124 this.$dirty = false;
23125 this.$pristine = true;
23126 this.$submitted = false;
23127 forEach(this.$$controls, function(control) {
Ed Tanous904063f2017-03-02 16:48:24 -080023128 control.$setPristine();
23129 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070023130 },
Ed Tanous904063f2017-03-02 16:48:24 -080023131
23132 /**
23133 * @ngdoc method
23134 * @name form.FormController#$setUntouched
23135 *
23136 * @description
23137 * Sets the form to its untouched state.
23138 *
23139 * This method can be called to remove the 'ng-touched' class and set the form controls to their
23140 * untouched state (ng-untouched class).
23141 *
23142 * Setting a form controls back to their untouched state is often useful when setting the form
23143 * back to its pristine state.
23144 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023145 $setUntouched: function() {
23146 forEach(this.$$controls, function(control) {
Ed Tanous904063f2017-03-02 16:48:24 -080023147 control.$setUntouched();
23148 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070023149 },
Ed Tanous904063f2017-03-02 16:48:24 -080023150
23151 /**
23152 * @ngdoc method
23153 * @name form.FormController#$setSubmitted
23154 *
23155 * @description
23156 * Sets the form to its submitted state.
23157 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070023158 $setSubmitted: function() {
23159 this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
23160 this.$submitted = true;
23161 this.$$parentForm.$setSubmitted();
23162 }
23163};
23164
23165/**
23166 * @ngdoc method
23167 * @name form.FormController#$setValidity
23168 *
23169 * @description
23170 * Sets the validity of a form control.
23171 *
23172 * This method will also propagate to parent forms.
23173 */
23174addSetValidityMethod({
23175 clazz: FormController,
23176 set: function(object, property, controller) {
23177 var list = object[property];
23178 if (!list) {
23179 object[property] = [controller];
23180 } else {
23181 var index = list.indexOf(controller);
23182 if (index === -1) {
23183 list.push(controller);
23184 }
23185 }
23186 },
23187 unset: function(object, property, controller) {
23188 var list = object[property];
23189 if (!list) {
23190 return;
23191 }
23192 arrayRemove(list, controller);
23193 if (list.length === 0) {
23194 delete object[property];
23195 }
23196 }
23197});
Ed Tanous904063f2017-03-02 16:48:24 -080023198
23199/**
23200 * @ngdoc directive
23201 * @name ngForm
23202 * @restrict EAC
23203 *
23204 * @description
23205 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
23206 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
23207 * sub-group of controls needs to be determined.
23208 *
23209 * Note: the purpose of `ngForm` is to group controls,
23210 * but not to be a replacement for the `<form>` tag with all of its capabilities
23211 * (e.g. posting to the server, ...).
23212 *
23213 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
23214 * related scope, under this name.
23215 *
23216 */
23217
23218 /**
23219 * @ngdoc directive
23220 * @name form
23221 * @restrict E
23222 *
23223 * @description
23224 * Directive that instantiates
23225 * {@link form.FormController FormController}.
23226 *
23227 * If the `name` attribute is specified, the form controller is published onto the current scope under
23228 * this name.
23229 *
23230 * # Alias: {@link ng.directive:ngForm `ngForm`}
23231 *
23232 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
23233 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
23234 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to
23235 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group
23236 * of controls needs to be determined.
23237 *
23238 * # CSS classes
23239 * - `ng-valid` is set if the form is valid.
23240 * - `ng-invalid` is set if the form is invalid.
23241 * - `ng-pending` is set if the form is pending.
23242 * - `ng-pristine` is set if the form is pristine.
23243 * - `ng-dirty` is set if the form is dirty.
23244 * - `ng-submitted` is set if the form was submitted.
23245 *
23246 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
23247 *
23248 *
23249 * # Submitting a form and preventing the default action
23250 *
23251 * Since the role of forms in client-side Angular applications is different than in classical
23252 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
23253 * page reload that sends the data to the server. Instead some javascript logic should be triggered
23254 * to handle the form submission in an application-specific way.
23255 *
23256 * For this reason, Angular prevents the default action (form submission to the server) unless the
23257 * `<form>` element has an `action` attribute specified.
23258 *
23259 * You can use one of the following two ways to specify what javascript method should be called when
23260 * a form is submitted:
23261 *
23262 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
23263 * - {@link ng.directive:ngClick ngClick} directive on the first
23264 * button or input field of type submit (input[type=submit])
23265 *
23266 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
23267 * or {@link ng.directive:ngClick ngClick} directives.
23268 * This is because of the following form submission rules in the HTML specification:
23269 *
23270 * - If a form has only one input field then hitting enter in this field triggers form submit
23271 * (`ngSubmit`)
23272 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
23273 * doesn't trigger submit
23274 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
23275 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
23276 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
23277 *
23278 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
23279 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
23280 * to have access to the updated model.
23281 *
23282 * ## Animation Hooks
23283 *
23284 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
23285 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
23286 * other validations that are performed within the form. Animations in ngForm are similar to how
23287 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
23288 * as JS animations.
23289 *
23290 * The following example shows a simple way to utilize CSS transitions to style a form element
23291 * that has been rendered as invalid after it has been validated:
23292 *
23293 * <pre>
23294 * //be sure to include ngAnimate as a module to hook into more
23295 * //advanced animations
23296 * .my-form {
23297 * transition:0.5s linear all;
23298 * background: white;
23299 * }
23300 * .my-form.ng-invalid {
23301 * background: red;
23302 * color:white;
23303 * }
23304 * </pre>
23305 *
23306 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070023307 <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
Ed Tanous904063f2017-03-02 16:48:24 -080023308 <file name="index.html">
23309 <script>
23310 angular.module('formExample', [])
23311 .controller('FormController', ['$scope', function($scope) {
23312 $scope.userType = 'guest';
23313 }]);
23314 </script>
23315 <style>
23316 .my-form {
23317 transition:all linear 0.5s;
23318 background: transparent;
23319 }
23320 .my-form.ng-invalid {
23321 background: red;
23322 }
23323 </style>
23324 <form name="myForm" ng-controller="FormController" class="my-form">
23325 userType: <input name="input" ng-model="userType" required>
23326 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
23327 <code>userType = {{userType}}</code><br>
23328 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
23329 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
23330 <code>myForm.$valid = {{myForm.$valid}}</code><br>
23331 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
23332 </form>
23333 </file>
23334 <file name="protractor.js" type="protractor">
23335 it('should initialize to model', function() {
23336 var userType = element(by.binding('userType'));
23337 var valid = element(by.binding('myForm.input.$valid'));
23338
23339 expect(userType.getText()).toContain('guest');
23340 expect(valid.getText()).toContain('true');
23341 });
23342
23343 it('should be invalid if empty', function() {
23344 var userType = element(by.binding('userType'));
23345 var valid = element(by.binding('myForm.input.$valid'));
23346 var userInput = element(by.model('userType'));
23347
23348 userInput.clear();
23349 userInput.sendKeys('');
23350
23351 expect(userType.getText()).toEqual('userType =');
23352 expect(valid.getText()).toContain('false');
23353 });
23354 </file>
23355 </example>
23356 *
23357 * @param {string=} name Name of the form. If specified, the form controller will be published into
23358 * related scope, under this name.
23359 */
23360var formDirectiveFactory = function(isNgForm) {
23361 return ['$timeout', '$parse', function($timeout, $parse) {
23362 var formDirective = {
23363 name: 'form',
23364 restrict: isNgForm ? 'EAC' : 'E',
23365 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
23366 controller: FormController,
23367 compile: function ngFormCompile(formElement, attr) {
23368 // Setup initial state of the control
23369 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
23370
23371 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
23372
23373 return {
23374 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
23375 var controller = ctrls[0];
23376
23377 // if `action` attr is not present on the form, prevent the default action (submission)
23378 if (!('action' in attr)) {
23379 // we can't use jq events because if a form is destroyed during submission the default
23380 // action is not prevented. see #1238
23381 //
23382 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
23383 // page reload if the form was destroyed by submission of the form via a click handler
23384 // on a button in the form. Looks like an IE9 specific bug.
23385 var handleFormSubmission = function(event) {
23386 scope.$apply(function() {
23387 controller.$commitViewValue();
23388 controller.$setSubmitted();
23389 });
23390
23391 event.preventDefault();
23392 };
23393
Ed Tanous4758d5b2017-06-06 15:28:13 -070023394 formElement[0].addEventListener('submit', handleFormSubmission);
Ed Tanous904063f2017-03-02 16:48:24 -080023395
23396 // unregister the preventDefault listener so that we don't not leak memory but in a
23397 // way that will achieve the prevention of the default action.
23398 formElement.on('$destroy', function() {
23399 $timeout(function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070023400 formElement[0].removeEventListener('submit', handleFormSubmission);
Ed Tanous904063f2017-03-02 16:48:24 -080023401 }, 0, false);
23402 });
23403 }
23404
23405 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
23406 parentFormCtrl.$addControl(controller);
23407
23408 var setter = nameAttr ? getSetter(controller.$name) : noop;
23409
23410 if (nameAttr) {
23411 setter(scope, controller);
23412 attr.$observe(nameAttr, function(newValue) {
23413 if (controller.$name === newValue) return;
23414 setter(scope, undefined);
23415 controller.$$parentForm.$$renameControl(controller, newValue);
23416 setter = getSetter(controller.$name);
23417 setter(scope, controller);
23418 });
23419 }
23420 formElement.on('$destroy', function() {
23421 controller.$$parentForm.$removeControl(controller);
23422 setter(scope, undefined);
23423 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
23424 });
23425 }
23426 };
23427 }
23428 };
23429
23430 return formDirective;
23431
23432 function getSetter(expression) {
23433 if (expression === '') {
23434 //create an assignable expression, so forms with an empty name can be renamed later
23435 return $parse('this[""]').assign;
23436 }
23437 return $parse(expression).assign || noop;
23438 }
23439 }];
23440};
23441
23442var formDirective = formDirectiveFactory();
23443var ngFormDirective = formDirectiveFactory(true);
23444
Ed Tanous4758d5b2017-06-06 15:28:13 -070023445
23446
23447// helper methods
23448function setupValidity(instance) {
23449 instance.$$classCache = {};
23450 instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS));
23451}
23452function addSetValidityMethod(context) {
23453 var clazz = context.clazz,
23454 set = context.set,
23455 unset = context.unset;
23456
23457 clazz.prototype.$setValidity = function(validationErrorKey, state, controller) {
23458 if (isUndefined(state)) {
23459 createAndSet(this, '$pending', validationErrorKey, controller);
23460 } else {
23461 unsetAndCleanup(this, '$pending', validationErrorKey, controller);
23462 }
23463 if (!isBoolean(state)) {
23464 unset(this.$error, validationErrorKey, controller);
23465 unset(this.$$success, validationErrorKey, controller);
23466 } else {
23467 if (state) {
23468 unset(this.$error, validationErrorKey, controller);
23469 set(this.$$success, validationErrorKey, controller);
23470 } else {
23471 set(this.$error, validationErrorKey, controller);
23472 unset(this.$$success, validationErrorKey, controller);
23473 }
23474 }
23475 if (this.$pending) {
23476 cachedToggleClass(this, PENDING_CLASS, true);
23477 this.$valid = this.$invalid = undefined;
23478 toggleValidationCss(this, '', null);
23479 } else {
23480 cachedToggleClass(this, PENDING_CLASS, false);
23481 this.$valid = isObjectEmpty(this.$error);
23482 this.$invalid = !this.$valid;
23483 toggleValidationCss(this, '', this.$valid);
23484 }
23485
23486 // re-read the state as the set/unset methods could have
23487 // combined state in this.$error[validationError] (used for forms),
23488 // where setting/unsetting only increments/decrements the value,
23489 // and does not replace it.
23490 var combinedState;
23491 if (this.$pending && this.$pending[validationErrorKey]) {
23492 combinedState = undefined;
23493 } else if (this.$error[validationErrorKey]) {
23494 combinedState = false;
23495 } else if (this.$$success[validationErrorKey]) {
23496 combinedState = true;
23497 } else {
23498 combinedState = null;
23499 }
23500
23501 toggleValidationCss(this, validationErrorKey, combinedState);
23502 this.$$parentForm.$setValidity(validationErrorKey, combinedState, this);
23503 };
23504
23505 function createAndSet(ctrl, name, value, controller) {
23506 if (!ctrl[name]) {
23507 ctrl[name] = {};
23508 }
23509 set(ctrl[name], value, controller);
23510 }
23511
23512 function unsetAndCleanup(ctrl, name, value, controller) {
23513 if (ctrl[name]) {
23514 unset(ctrl[name], value, controller);
23515 }
23516 if (isObjectEmpty(ctrl[name])) {
23517 ctrl[name] = undefined;
23518 }
23519 }
23520
23521 function cachedToggleClass(ctrl, className, switchValue) {
23522 if (switchValue && !ctrl.$$classCache[className]) {
23523 ctrl.$$animate.addClass(ctrl.$$element, className);
23524 ctrl.$$classCache[className] = true;
23525 } else if (!switchValue && ctrl.$$classCache[className]) {
23526 ctrl.$$animate.removeClass(ctrl.$$element, className);
23527 ctrl.$$classCache[className] = false;
23528 }
23529 }
23530
23531 function toggleValidationCss(ctrl, validationErrorKey, isValid) {
23532 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
23533
23534 cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true);
23535 cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false);
23536 }
23537}
23538
23539function isObjectEmpty(obj) {
23540 if (obj) {
23541 for (var prop in obj) {
23542 if (obj.hasOwnProperty(prop)) {
23543 return false;
23544 }
23545 }
23546 }
23547 return true;
23548}
23549
23550/* global
23551 VALID_CLASS: false,
Ed Tanous904063f2017-03-02 16:48:24 -080023552 INVALID_CLASS: false,
23553 PRISTINE_CLASS: false,
23554 DIRTY_CLASS: false,
Ed Tanous4758d5b2017-06-06 15:28:13 -070023555 ngModelMinErr: false
Ed Tanous904063f2017-03-02 16:48:24 -080023556*/
23557
23558// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
23559var 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)$/;
23560// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
23561// Note: We are being more lenient, because browsers are too.
23562// 1. Scheme
23563// 2. Slashes
23564// 3. Username
23565// 4. Password
23566// 5. Hostname
23567// 6. Port
23568// 7. Path
23569// 8. Query
23570// 9. Fragment
Ed Tanous4758d5b2017-06-06 15:28:13 -070023571// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999
23572var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
23573// eslint-disable-next-line max-len
23574var 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])?)*$/;
23575var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
Ed Tanous904063f2017-03-02 16:48:24 -080023576var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
23577var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
23578var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
23579var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
23580var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
23581
23582var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
23583var PARTIAL_VALIDATION_TYPES = createMap();
23584forEach('date,datetime-local,month,time,week'.split(','), function(type) {
23585 PARTIAL_VALIDATION_TYPES[type] = true;
23586});
23587
23588var inputType = {
23589
23590 /**
23591 * @ngdoc input
23592 * @name input[text]
23593 *
23594 * @description
23595 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
23596 *
23597 *
23598 * @param {string} ngModel Assignable angular expression to data-bind to.
23599 * @param {string=} name Property name of the form under which the control is published.
23600 * @param {string=} required Adds `required` validation error key if the value is not entered.
23601 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
23602 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
23603 * `required` when you want to data-bind to the `required` attribute.
23604 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
23605 * minlength.
23606 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
23607 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
23608 * any length.
23609 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
23610 * that contains the regular expression body that will be converted to a regular expression
23611 * as in the ngPattern directive.
23612 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
23613 * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
23614 * If the expression evaluates to a RegExp object, then this is used directly.
23615 * If the expression evaluates to a string, then it will be converted to a RegExp
23616 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
23617 * `new RegExp('^abc$')`.<br />
23618 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
23619 * start at the index of the last search's match, thus not taking the whole input value into
23620 * account.
23621 * @param {string=} ngChange Angular expression to be executed when input changes due to user
23622 * interaction with the input element.
23623 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
23624 * This parameter is ignored for input[type=password] controls, which will never trim the
23625 * input.
23626 *
23627 * @example
23628 <example name="text-input-directive" module="textInputExample">
23629 <file name="index.html">
23630 <script>
23631 angular.module('textInputExample', [])
23632 .controller('ExampleController', ['$scope', function($scope) {
23633 $scope.example = {
23634 text: 'guest',
23635 word: /^\s*\w*\s*$/
23636 };
23637 }]);
23638 </script>
23639 <form name="myForm" ng-controller="ExampleController">
23640 <label>Single word:
23641 <input type="text" name="input" ng-model="example.text"
23642 ng-pattern="example.word" required ng-trim="false">
23643 </label>
23644 <div role="alert">
23645 <span class="error" ng-show="myForm.input.$error.required">
23646 Required!</span>
23647 <span class="error" ng-show="myForm.input.$error.pattern">
23648 Single word only!</span>
23649 </div>
23650 <code>text = {{example.text}}</code><br/>
23651 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/>
23652 <code>myForm.input.$error = {{myForm.input.$error}}</code><br/>
23653 <code>myForm.$valid = {{myForm.$valid}}</code><br/>
23654 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/>
23655 </form>
23656 </file>
23657 <file name="protractor.js" type="protractor">
23658 var text = element(by.binding('example.text'));
23659 var valid = element(by.binding('myForm.input.$valid'));
23660 var input = element(by.model('example.text'));
23661
23662 it('should initialize to model', function() {
23663 expect(text.getText()).toContain('guest');
23664 expect(valid.getText()).toContain('true');
23665 });
23666
23667 it('should be invalid if empty', function() {
23668 input.clear();
23669 input.sendKeys('');
23670
23671 expect(text.getText()).toEqual('text =');
23672 expect(valid.getText()).toContain('false');
23673 });
23674
23675 it('should be invalid if multi word', function() {
23676 input.clear();
23677 input.sendKeys('hello world');
23678
23679 expect(valid.getText()).toContain('false');
23680 });
23681 </file>
23682 </example>
23683 */
23684 'text': textInputType,
23685
23686 /**
23687 * @ngdoc input
23688 * @name input[date]
23689 *
23690 * @description
23691 * Input with date validation and transformation. In browsers that do not yet support
23692 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
23693 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
23694 * modern browsers do not yet support this input type, it is important to provide cues to users on the
23695 * expected input format via a placeholder or label.
23696 *
23697 * The model must always be a Date object, otherwise Angular will throw an error.
23698 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
23699 *
23700 * The timezone to be used to read/write the `Date` instance in the model can be defined using
23701 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
23702 *
23703 * @param {string} ngModel Assignable angular expression to data-bind to.
23704 * @param {string=} name Property name of the form under which the control is published.
23705 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
23706 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
23707 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
23708 * constraint validation.
23709 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
23710 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
23711 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
23712 * constraint validation.
23713 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
23714 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
23715 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
23716 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
23717 * @param {string=} required Sets `required` validation error key if the value is not entered.
23718 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
23719 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
23720 * `required` when you want to data-bind to the `required` attribute.
23721 * @param {string=} ngChange Angular expression to be executed when input changes due to user
23722 * interaction with the input element.
23723 *
23724 * @example
23725 <example name="date-input-directive" module="dateInputExample">
23726 <file name="index.html">
23727 <script>
23728 angular.module('dateInputExample', [])
23729 .controller('DateController', ['$scope', function($scope) {
23730 $scope.example = {
23731 value: new Date(2013, 9, 22)
23732 };
23733 }]);
23734 </script>
23735 <form name="myForm" ng-controller="DateController as dateCtrl">
23736 <label for="exampleInput">Pick a date in 2013:</label>
23737 <input type="date" id="exampleInput" name="input" ng-model="example.value"
23738 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
23739 <div role="alert">
23740 <span class="error" ng-show="myForm.input.$error.required">
23741 Required!</span>
23742 <span class="error" ng-show="myForm.input.$error.date">
23743 Not a valid date!</span>
23744 </div>
23745 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
23746 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
23747 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
23748 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
23749 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
23750 </form>
23751 </file>
23752 <file name="protractor.js" type="protractor">
23753 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
23754 var valid = element(by.binding('myForm.input.$valid'));
Ed Tanous904063f2017-03-02 16:48:24 -080023755
23756 // currently protractor/webdriver does not support
23757 // sending keys to all known HTML5 input controls
23758 // for various browsers (see https://github.com/angular/protractor/issues/562).
23759 function setInput(val) {
23760 // set the value of the element and force validation.
23761 var scr = "var ipt = document.getElementById('exampleInput'); " +
23762 "ipt.value = '" + val + "';" +
23763 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
23764 browser.executeScript(scr);
23765 }
23766
23767 it('should initialize to model', function() {
23768 expect(value.getText()).toContain('2013-10-22');
23769 expect(valid.getText()).toContain('myForm.input.$valid = true');
23770 });
23771
23772 it('should be invalid if empty', function() {
23773 setInput('');
23774 expect(value.getText()).toEqual('value =');
23775 expect(valid.getText()).toContain('myForm.input.$valid = false');
23776 });
23777
23778 it('should be invalid if over max', function() {
23779 setInput('2015-01-01');
23780 expect(value.getText()).toContain('');
23781 expect(valid.getText()).toContain('myForm.input.$valid = false');
23782 });
23783 </file>
23784 </example>
23785 */
23786 'date': createDateInputType('date', DATE_REGEXP,
23787 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
23788 'yyyy-MM-dd'),
23789
23790 /**
23791 * @ngdoc input
23792 * @name input[datetime-local]
23793 *
23794 * @description
23795 * Input with datetime validation and transformation. In browsers that do not yet support
23796 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
23797 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
23798 *
23799 * The model must always be a Date object, otherwise Angular will throw an error.
23800 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
23801 *
23802 * The timezone to be used to read/write the `Date` instance in the model can be defined using
23803 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
23804 *
23805 * @param {string} ngModel Assignable angular expression to data-bind to.
23806 * @param {string=} name Property name of the form under which the control is published.
23807 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
23808 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
23809 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
23810 * Note that `min` will also add native HTML5 constraint validation.
23811 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
23812 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
23813 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
23814 * Note that `max` will also add native HTML5 constraint validation.
23815 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
23816 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
23817 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
23818 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
23819 * @param {string=} required Sets `required` validation error key if the value is not entered.
23820 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
23821 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
23822 * `required` when you want to data-bind to the `required` attribute.
23823 * @param {string=} ngChange Angular expression to be executed when input changes due to user
23824 * interaction with the input element.
23825 *
23826 * @example
23827 <example name="datetimelocal-input-directive" module="dateExample">
23828 <file name="index.html">
23829 <script>
23830 angular.module('dateExample', [])
23831 .controller('DateController', ['$scope', function($scope) {
23832 $scope.example = {
23833 value: new Date(2010, 11, 28, 14, 57)
23834 };
23835 }]);
23836 </script>
23837 <form name="myForm" ng-controller="DateController as dateCtrl">
23838 <label for="exampleInput">Pick a date between in 2013:</label>
23839 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
23840 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
23841 <div role="alert">
23842 <span class="error" ng-show="myForm.input.$error.required">
23843 Required!</span>
23844 <span class="error" ng-show="myForm.input.$error.datetimelocal">
23845 Not a valid date!</span>
23846 </div>
23847 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
23848 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
23849 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
23850 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
23851 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
23852 </form>
23853 </file>
23854 <file name="protractor.js" type="protractor">
23855 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
23856 var valid = element(by.binding('myForm.input.$valid'));
Ed Tanous904063f2017-03-02 16:48:24 -080023857
23858 // currently protractor/webdriver does not support
23859 // sending keys to all known HTML5 input controls
23860 // for various browsers (https://github.com/angular/protractor/issues/562).
23861 function setInput(val) {
23862 // set the value of the element and force validation.
23863 var scr = "var ipt = document.getElementById('exampleInput'); " +
23864 "ipt.value = '" + val + "';" +
23865 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
23866 browser.executeScript(scr);
23867 }
23868
23869 it('should initialize to model', function() {
23870 expect(value.getText()).toContain('2010-12-28T14:57:00');
23871 expect(valid.getText()).toContain('myForm.input.$valid = true');
23872 });
23873
23874 it('should be invalid if empty', function() {
23875 setInput('');
23876 expect(value.getText()).toEqual('value =');
23877 expect(valid.getText()).toContain('myForm.input.$valid = false');
23878 });
23879
23880 it('should be invalid if over max', function() {
23881 setInput('2015-01-01T23:59:00');
23882 expect(value.getText()).toContain('');
23883 expect(valid.getText()).toContain('myForm.input.$valid = false');
23884 });
23885 </file>
23886 </example>
23887 */
23888 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
23889 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
23890 'yyyy-MM-ddTHH:mm:ss.sss'),
23891
23892 /**
23893 * @ngdoc input
23894 * @name input[time]
23895 *
23896 * @description
23897 * Input with time validation and transformation. In browsers that do not yet support
23898 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
23899 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
23900 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
23901 *
23902 * The model must always be a Date object, otherwise Angular will throw an error.
23903 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
23904 *
23905 * The timezone to be used to read/write the `Date` instance in the model can be defined using
23906 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
23907 *
23908 * @param {string} ngModel Assignable angular expression to data-bind to.
23909 * @param {string=} name Property name of the form under which the control is published.
23910 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
23911 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
23912 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
23913 * native HTML5 constraint validation.
23914 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
23915 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
23916 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
23917 * native HTML5 constraint validation.
23918 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
23919 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
23920 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
23921 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
23922 * @param {string=} required Sets `required` validation error key if the value is not entered.
23923 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
23924 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
23925 * `required` when you want to data-bind to the `required` attribute.
23926 * @param {string=} ngChange Angular expression to be executed when input changes due to user
23927 * interaction with the input element.
23928 *
23929 * @example
23930 <example name="time-input-directive" module="timeExample">
23931 <file name="index.html">
23932 <script>
23933 angular.module('timeExample', [])
23934 .controller('DateController', ['$scope', function($scope) {
23935 $scope.example = {
23936 value: new Date(1970, 0, 1, 14, 57, 0)
23937 };
23938 }]);
23939 </script>
23940 <form name="myForm" ng-controller="DateController as dateCtrl">
23941 <label for="exampleInput">Pick a time between 8am and 5pm:</label>
23942 <input type="time" id="exampleInput" name="input" ng-model="example.value"
23943 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
23944 <div role="alert">
23945 <span class="error" ng-show="myForm.input.$error.required">
23946 Required!</span>
23947 <span class="error" ng-show="myForm.input.$error.time">
23948 Not a valid date!</span>
23949 </div>
23950 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
23951 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
23952 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
23953 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
23954 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
23955 </form>
23956 </file>
23957 <file name="protractor.js" type="protractor">
23958 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
23959 var valid = element(by.binding('myForm.input.$valid'));
Ed Tanous904063f2017-03-02 16:48:24 -080023960
23961 // currently protractor/webdriver does not support
23962 // sending keys to all known HTML5 input controls
23963 // for various browsers (https://github.com/angular/protractor/issues/562).
23964 function setInput(val) {
23965 // set the value of the element and force validation.
23966 var scr = "var ipt = document.getElementById('exampleInput'); " +
23967 "ipt.value = '" + val + "';" +
23968 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
23969 browser.executeScript(scr);
23970 }
23971
23972 it('should initialize to model', function() {
23973 expect(value.getText()).toContain('14:57:00');
23974 expect(valid.getText()).toContain('myForm.input.$valid = true');
23975 });
23976
23977 it('should be invalid if empty', function() {
23978 setInput('');
23979 expect(value.getText()).toEqual('value =');
23980 expect(valid.getText()).toContain('myForm.input.$valid = false');
23981 });
23982
23983 it('should be invalid if over max', function() {
23984 setInput('23:59:00');
23985 expect(value.getText()).toContain('');
23986 expect(valid.getText()).toContain('myForm.input.$valid = false');
23987 });
23988 </file>
23989 </example>
23990 */
23991 'time': createDateInputType('time', TIME_REGEXP,
23992 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
23993 'HH:mm:ss.sss'),
23994
23995 /**
23996 * @ngdoc input
23997 * @name input[week]
23998 *
23999 * @description
24000 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
24001 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
24002 * week format (yyyy-W##), for example: `2013-W02`.
24003 *
24004 * The model must always be a Date object, otherwise Angular will throw an error.
24005 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
24006 *
24007 * The timezone to be used to read/write the `Date` instance in the model can be defined using
24008 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
24009 *
24010 * @param {string} ngModel Assignable angular expression to data-bind to.
24011 * @param {string=} name Property name of the form under which the control is published.
24012 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24013 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
24014 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
24015 * native HTML5 constraint validation.
24016 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24017 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
24018 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
24019 * native HTML5 constraint validation.
24020 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
24021 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
24022 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
24023 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
24024 * @param {string=} required Sets `required` validation error key if the value is not entered.
24025 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24026 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24027 * `required` when you want to data-bind to the `required` attribute.
24028 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24029 * interaction with the input element.
24030 *
24031 * @example
24032 <example name="week-input-directive" module="weekExample">
24033 <file name="index.html">
24034 <script>
24035 angular.module('weekExample', [])
24036 .controller('DateController', ['$scope', function($scope) {
24037 $scope.example = {
24038 value: new Date(2013, 0, 3)
24039 };
24040 }]);
24041 </script>
24042 <form name="myForm" ng-controller="DateController as dateCtrl">
24043 <label>Pick a date between in 2013:
24044 <input id="exampleInput" type="week" name="input" ng-model="example.value"
24045 placeholder="YYYY-W##" min="2012-W32"
24046 max="2013-W52" required />
24047 </label>
24048 <div role="alert">
24049 <span class="error" ng-show="myForm.input.$error.required">
24050 Required!</span>
24051 <span class="error" ng-show="myForm.input.$error.week">
24052 Not a valid date!</span>
24053 </div>
24054 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
24055 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24056 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24057 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24058 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24059 </form>
24060 </file>
24061 <file name="protractor.js" type="protractor">
24062 var value = element(by.binding('example.value | date: "yyyy-Www"'));
24063 var valid = element(by.binding('myForm.input.$valid'));
Ed Tanous904063f2017-03-02 16:48:24 -080024064
24065 // currently protractor/webdriver does not support
24066 // sending keys to all known HTML5 input controls
24067 // for various browsers (https://github.com/angular/protractor/issues/562).
24068 function setInput(val) {
24069 // set the value of the element and force validation.
24070 var scr = "var ipt = document.getElementById('exampleInput'); " +
24071 "ipt.value = '" + val + "';" +
24072 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24073 browser.executeScript(scr);
24074 }
24075
24076 it('should initialize to model', function() {
24077 expect(value.getText()).toContain('2013-W01');
24078 expect(valid.getText()).toContain('myForm.input.$valid = true');
24079 });
24080
24081 it('should be invalid if empty', function() {
24082 setInput('');
24083 expect(value.getText()).toEqual('value =');
24084 expect(valid.getText()).toContain('myForm.input.$valid = false');
24085 });
24086
24087 it('should be invalid if over max', function() {
24088 setInput('2015-W01');
24089 expect(value.getText()).toContain('');
24090 expect(valid.getText()).toContain('myForm.input.$valid = false');
24091 });
24092 </file>
24093 </example>
24094 */
24095 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
24096
24097 /**
24098 * @ngdoc input
24099 * @name input[month]
24100 *
24101 * @description
24102 * Input with month validation and transformation. In browsers that do not yet support
24103 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
24104 * month format (yyyy-MM), for example: `2009-01`.
24105 *
24106 * The model must always be a Date object, otherwise Angular will throw an error.
24107 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
24108 * If the model is not set to the first of the month, the next view to model update will set it
24109 * to the first of the month.
24110 *
24111 * The timezone to be used to read/write the `Date` instance in the model can be defined using
24112 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
24113 *
24114 * @param {string} ngModel Assignable angular expression to data-bind to.
24115 * @param {string=} name Property name of the form under which the control is published.
24116 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24117 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
24118 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
24119 * native HTML5 constraint validation.
24120 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24121 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
24122 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
24123 * native HTML5 constraint validation.
24124 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
24125 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
24126 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
24127 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
24128
24129 * @param {string=} required Sets `required` validation error key if the value is not entered.
24130 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24131 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24132 * `required` when you want to data-bind to the `required` attribute.
24133 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24134 * interaction with the input element.
24135 *
24136 * @example
24137 <example name="month-input-directive" module="monthExample">
24138 <file name="index.html">
24139 <script>
24140 angular.module('monthExample', [])
24141 .controller('DateController', ['$scope', function($scope) {
24142 $scope.example = {
24143 value: new Date(2013, 9, 1)
24144 };
24145 }]);
24146 </script>
24147 <form name="myForm" ng-controller="DateController as dateCtrl">
24148 <label for="exampleInput">Pick a month in 2013:</label>
24149 <input id="exampleInput" type="month" name="input" ng-model="example.value"
24150 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
24151 <div role="alert">
24152 <span class="error" ng-show="myForm.input.$error.required">
24153 Required!</span>
24154 <span class="error" ng-show="myForm.input.$error.month">
24155 Not a valid month!</span>
24156 </div>
24157 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
24158 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24159 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24160 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24161 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24162 </form>
24163 </file>
24164 <file name="protractor.js" type="protractor">
24165 var value = element(by.binding('example.value | date: "yyyy-MM"'));
24166 var valid = element(by.binding('myForm.input.$valid'));
Ed Tanous904063f2017-03-02 16:48:24 -080024167
24168 // currently protractor/webdriver does not support
24169 // sending keys to all known HTML5 input controls
24170 // for various browsers (https://github.com/angular/protractor/issues/562).
24171 function setInput(val) {
24172 // set the value of the element and force validation.
24173 var scr = "var ipt = document.getElementById('exampleInput'); " +
24174 "ipt.value = '" + val + "';" +
24175 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24176 browser.executeScript(scr);
24177 }
24178
24179 it('should initialize to model', function() {
24180 expect(value.getText()).toContain('2013-10');
24181 expect(valid.getText()).toContain('myForm.input.$valid = true');
24182 });
24183
24184 it('should be invalid if empty', function() {
24185 setInput('');
24186 expect(value.getText()).toEqual('value =');
24187 expect(valid.getText()).toContain('myForm.input.$valid = false');
24188 });
24189
24190 it('should be invalid if over max', function() {
24191 setInput('2015-01');
24192 expect(value.getText()).toContain('');
24193 expect(valid.getText()).toContain('myForm.input.$valid = false');
24194 });
24195 </file>
24196 </example>
24197 */
24198 'month': createDateInputType('month', MONTH_REGEXP,
24199 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
24200 'yyyy-MM'),
24201
24202 /**
24203 * @ngdoc input
24204 * @name input[number]
24205 *
24206 * @description
24207 * Text input with number validation and transformation. Sets the `number` validation
24208 * error if not a valid number.
24209 *
24210 * <div class="alert alert-warning">
24211 * The model must always be of type `number` otherwise Angular will throw an error.
24212 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
24213 * error docs for more information and an example of how to convert your model if necessary.
24214 * </div>
24215 *
24216 * ## Issues with HTML5 constraint validation
24217 *
24218 * In browsers that follow the
24219 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
24220 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
24221 * If a non-number is entered in the input, the browser will report the value as an empty string,
24222 * which means the view / model values in `ngModel` and subsequently the scope value
24223 * will also be an empty string.
24224 *
24225 *
24226 * @param {string} ngModel Assignable angular expression to data-bind to.
24227 * @param {string=} name Property name of the form under which the control is published.
24228 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070024229 * Can be interpolated.
Ed Tanous904063f2017-03-02 16:48:24 -080024230 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
Ed Tanous4758d5b2017-06-06 15:28:13 -070024231 * Can be interpolated.
24232 * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`,
24233 * but does not trigger HTML5 native validation. Takes an expression.
24234 * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`,
24235 * but does not trigger HTML5 native validation. Takes an expression.
24236 * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint.
24237 * Can be interpolated.
24238 * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint,
24239 * but does not trigger HTML5 native validation. Takes an expression.
Ed Tanous904063f2017-03-02 16:48:24 -080024240 * @param {string=} required Sets `required` validation error key if the value is not entered.
24241 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24242 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24243 * `required` when you want to data-bind to the `required` attribute.
24244 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
24245 * minlength.
24246 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
24247 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
24248 * any length.
24249 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
24250 * that contains the regular expression body that will be converted to a regular expression
24251 * as in the ngPattern directive.
24252 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
24253 * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
24254 * If the expression evaluates to a RegExp object, then this is used directly.
24255 * If the expression evaluates to a string, then it will be converted to a RegExp
24256 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
24257 * `new RegExp('^abc$')`.<br />
24258 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
24259 * start at the index of the last search's match, thus not taking the whole input value into
24260 * account.
24261 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24262 * interaction with the input element.
24263 *
24264 * @example
24265 <example name="number-input-directive" module="numberExample">
24266 <file name="index.html">
24267 <script>
24268 angular.module('numberExample', [])
24269 .controller('ExampleController', ['$scope', function($scope) {
24270 $scope.example = {
24271 value: 12
24272 };
24273 }]);
24274 </script>
24275 <form name="myForm" ng-controller="ExampleController">
24276 <label>Number:
24277 <input type="number" name="input" ng-model="example.value"
24278 min="0" max="99" required>
24279 </label>
24280 <div role="alert">
24281 <span class="error" ng-show="myForm.input.$error.required">
24282 Required!</span>
24283 <span class="error" ng-show="myForm.input.$error.number">
24284 Not valid number!</span>
24285 </div>
24286 <tt>value = {{example.value}}</tt><br/>
24287 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24288 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24289 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24290 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24291 </form>
24292 </file>
24293 <file name="protractor.js" type="protractor">
24294 var value = element(by.binding('example.value'));
24295 var valid = element(by.binding('myForm.input.$valid'));
24296 var input = element(by.model('example.value'));
24297
24298 it('should initialize to model', function() {
24299 expect(value.getText()).toContain('12');
24300 expect(valid.getText()).toContain('true');
24301 });
24302
24303 it('should be invalid if empty', function() {
24304 input.clear();
24305 input.sendKeys('');
24306 expect(value.getText()).toEqual('value =');
24307 expect(valid.getText()).toContain('false');
24308 });
24309
24310 it('should be invalid if over max', function() {
24311 input.clear();
24312 input.sendKeys('123');
24313 expect(value.getText()).toEqual('value =');
24314 expect(valid.getText()).toContain('false');
24315 });
24316 </file>
24317 </example>
24318 */
24319 'number': numberInputType,
24320
24321
24322 /**
24323 * @ngdoc input
24324 * @name input[url]
24325 *
24326 * @description
24327 * Text input with URL validation. Sets the `url` validation error key if the content is not a
24328 * valid URL.
24329 *
24330 * <div class="alert alert-warning">
24331 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
24332 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
24333 * the built-in validators (see the {@link guide/forms Forms guide})
24334 * </div>
24335 *
24336 * @param {string} ngModel Assignable angular expression to data-bind to.
24337 * @param {string=} name Property name of the form under which the control is published.
24338 * @param {string=} required Sets `required` validation error key if the value is not entered.
24339 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24340 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24341 * `required` when you want to data-bind to the `required` attribute.
24342 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
24343 * minlength.
24344 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
24345 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
24346 * any length.
24347 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
24348 * that contains the regular expression body that will be converted to a regular expression
24349 * as in the ngPattern directive.
24350 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
24351 * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
24352 * If the expression evaluates to a RegExp object, then this is used directly.
24353 * If the expression evaluates to a string, then it will be converted to a RegExp
24354 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
24355 * `new RegExp('^abc$')`.<br />
24356 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
24357 * start at the index of the last search's match, thus not taking the whole input value into
24358 * account.
24359 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24360 * interaction with the input element.
24361 *
24362 * @example
24363 <example name="url-input-directive" module="urlExample">
24364 <file name="index.html">
24365 <script>
24366 angular.module('urlExample', [])
24367 .controller('ExampleController', ['$scope', function($scope) {
24368 $scope.url = {
24369 text: 'http://google.com'
24370 };
24371 }]);
24372 </script>
24373 <form name="myForm" ng-controller="ExampleController">
24374 <label>URL:
24375 <input type="url" name="input" ng-model="url.text" required>
24376 <label>
24377 <div role="alert">
24378 <span class="error" ng-show="myForm.input.$error.required">
24379 Required!</span>
24380 <span class="error" ng-show="myForm.input.$error.url">
24381 Not valid url!</span>
24382 </div>
24383 <tt>text = {{url.text}}</tt><br/>
24384 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24385 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24386 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24387 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24388 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
24389 </form>
24390 </file>
24391 <file name="protractor.js" type="protractor">
24392 var text = element(by.binding('url.text'));
24393 var valid = element(by.binding('myForm.input.$valid'));
24394 var input = element(by.model('url.text'));
24395
24396 it('should initialize to model', function() {
24397 expect(text.getText()).toContain('http://google.com');
24398 expect(valid.getText()).toContain('true');
24399 });
24400
24401 it('should be invalid if empty', function() {
24402 input.clear();
24403 input.sendKeys('');
24404
24405 expect(text.getText()).toEqual('text =');
24406 expect(valid.getText()).toContain('false');
24407 });
24408
24409 it('should be invalid if not url', function() {
24410 input.clear();
24411 input.sendKeys('box');
24412
24413 expect(valid.getText()).toContain('false');
24414 });
24415 </file>
24416 </example>
24417 */
24418 'url': urlInputType,
24419
24420
24421 /**
24422 * @ngdoc input
24423 * @name input[email]
24424 *
24425 * @description
24426 * Text input with email validation. Sets the `email` validation error key if not a valid email
24427 * address.
24428 *
24429 * <div class="alert alert-warning">
24430 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
24431 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
24432 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
24433 * </div>
24434 *
24435 * @param {string} ngModel Assignable angular expression to data-bind to.
24436 * @param {string=} name Property name of the form under which the control is published.
24437 * @param {string=} required Sets `required` validation error key if the value is not entered.
24438 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24439 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24440 * `required` when you want to data-bind to the `required` attribute.
24441 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
24442 * minlength.
24443 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
24444 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
24445 * any length.
24446 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
24447 * that contains the regular expression body that will be converted to a regular expression
24448 * as in the ngPattern directive.
24449 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
24450 * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
24451 * If the expression evaluates to a RegExp object, then this is used directly.
24452 * If the expression evaluates to a string, then it will be converted to a RegExp
24453 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
24454 * `new RegExp('^abc$')`.<br />
24455 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
24456 * start at the index of the last search's match, thus not taking the whole input value into
24457 * account.
24458 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24459 * interaction with the input element.
24460 *
24461 * @example
24462 <example name="email-input-directive" module="emailExample">
24463 <file name="index.html">
24464 <script>
24465 angular.module('emailExample', [])
24466 .controller('ExampleController', ['$scope', function($scope) {
24467 $scope.email = {
24468 text: 'me@example.com'
24469 };
24470 }]);
24471 </script>
24472 <form name="myForm" ng-controller="ExampleController">
24473 <label>Email:
24474 <input type="email" name="input" ng-model="email.text" required>
24475 </label>
24476 <div role="alert">
24477 <span class="error" ng-show="myForm.input.$error.required">
24478 Required!</span>
24479 <span class="error" ng-show="myForm.input.$error.email">
24480 Not valid email!</span>
24481 </div>
24482 <tt>text = {{email.text}}</tt><br/>
24483 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24484 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24485 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24486 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24487 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
24488 </form>
24489 </file>
24490 <file name="protractor.js" type="protractor">
24491 var text = element(by.binding('email.text'));
24492 var valid = element(by.binding('myForm.input.$valid'));
24493 var input = element(by.model('email.text'));
24494
24495 it('should initialize to model', function() {
24496 expect(text.getText()).toContain('me@example.com');
24497 expect(valid.getText()).toContain('true');
24498 });
24499
24500 it('should be invalid if empty', function() {
24501 input.clear();
24502 input.sendKeys('');
24503 expect(text.getText()).toEqual('text =');
24504 expect(valid.getText()).toContain('false');
24505 });
24506
24507 it('should be invalid if not email', function() {
24508 input.clear();
24509 input.sendKeys('xxx');
24510
24511 expect(valid.getText()).toContain('false');
24512 });
24513 </file>
24514 </example>
24515 */
24516 'email': emailInputType,
24517
24518
24519 /**
24520 * @ngdoc input
24521 * @name input[radio]
24522 *
24523 * @description
24524 * HTML radio button.
24525 *
24526 * @param {string} ngModel Assignable angular expression to data-bind to.
24527 * @param {string} value The value to which the `ngModel` expression should be set when selected.
24528 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
24529 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
24530 * @param {string=} name Property name of the form under which the control is published.
24531 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24532 * interaction with the input element.
24533 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
24534 * is selected. Should be used instead of the `value` attribute if you need
24535 * a non-string `ngModel` (`boolean`, `array`, ...).
24536 *
24537 * @example
24538 <example name="radio-input-directive" module="radioExample">
24539 <file name="index.html">
24540 <script>
24541 angular.module('radioExample', [])
24542 .controller('ExampleController', ['$scope', function($scope) {
24543 $scope.color = {
24544 name: 'blue'
24545 };
24546 $scope.specialValue = {
24547 "id": "12345",
24548 "value": "green"
24549 };
24550 }]);
24551 </script>
24552 <form name="myForm" ng-controller="ExampleController">
24553 <label>
24554 <input type="radio" ng-model="color.name" value="red">
24555 Red
24556 </label><br/>
24557 <label>
24558 <input type="radio" ng-model="color.name" ng-value="specialValue">
24559 Green
24560 </label><br/>
24561 <label>
24562 <input type="radio" ng-model="color.name" value="blue">
24563 Blue
24564 </label><br/>
24565 <tt>color = {{color.name | json}}</tt><br/>
24566 </form>
24567 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
24568 </file>
24569 <file name="protractor.js" type="protractor">
24570 it('should change state', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070024571 var inputs = element.all(by.model('color.name'));
Ed Tanous904063f2017-03-02 16:48:24 -080024572 var color = element(by.binding('color.name'));
24573
24574 expect(color.getText()).toContain('blue');
24575
Ed Tanous4758d5b2017-06-06 15:28:13 -070024576 inputs.get(0).click();
Ed Tanous904063f2017-03-02 16:48:24 -080024577 expect(color.getText()).toContain('red');
Ed Tanous4758d5b2017-06-06 15:28:13 -070024578
24579 inputs.get(1).click();
24580 expect(color.getText()).toContain('green');
Ed Tanous904063f2017-03-02 16:48:24 -080024581 });
24582 </file>
24583 </example>
24584 */
24585 'radio': radioInputType,
24586
Ed Tanous4758d5b2017-06-06 15:28:13 -070024587 /**
24588 * @ngdoc input
24589 * @name input[range]
24590 *
24591 * @description
24592 * Native range input with validation and transformation.
24593 *
24594 * The model for the range input must always be a `Number`.
24595 *
24596 * IE9 and other browsers that do not support the `range` type fall back
24597 * to a text input without any default values for `min`, `max` and `step`. Model binding,
24598 * validation and number parsing are nevertheless supported.
24599 *
24600 * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
24601 * in a way that never allows the input to hold an invalid value. That means:
24602 * - any non-numerical value is set to `(max + min) / 2`.
24603 * - any numerical value that is less than the current min val, or greater than the current max val
24604 * is set to the min / max val respectively.
24605 * - additionally, the current `step` is respected, so the nearest value that satisfies a step
24606 * is used.
24607 *
24608 * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range))
24609 * for more info.
24610 *
24611 * This has the following consequences for Angular:
24612 *
24613 * Since the element value should always reflect the current model value, a range input
24614 * will set the bound ngModel expression to the value that the browser has set for the
24615 * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
24616 * if the application sets `model.value = null`, the browser will set the input to `'50'`.
24617 * Angular will then set the model to `50`, to prevent input and model value being out of sync.
24618 *
24619 * That means the model for range will immediately be set to `50` after `ngModel` has been
24620 * initialized. It also means a range input can never have the required error.
24621 *
24622 * This does not only affect changes to the model value, but also to the values of the `min`,
24623 * `max`, and `step` attributes. When these change in a way that will cause the browser to modify
24624 * the input value, Angular will also update the model value.
24625 *
24626 * Automatic value adjustment also means that a range input element can never have the `required`,
24627 * `min`, or `max` errors.
24628 *
24629 * However, `step` is currently only fully implemented by Firefox. Other browsers have problems
24630 * when the step value changes dynamically - they do not adjust the element value correctly, but
24631 * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step`
24632 * error on the input, and set the model to `undefined`.
24633 *
24634 * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
24635 * not set the `min` and `max` attributes, which means that the browser won't automatically adjust
24636 * the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
24637 *
24638 * @param {string} ngModel Assignable angular expression to data-bind to.
24639 * @param {string=} name Property name of the form under which the control is published.
24640 * @param {string=} min Sets the `min` validation to ensure that the value entered is greater
24641 * than `min`. Can be interpolated.
24642 * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
24643 * Can be interpolated.
24644 * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
24645 * Can be interpolated.
24646 * @param {string=} ngChange Angular expression to be executed when the ngModel value changes due
24647 * to user interaction with the input element.
24648 * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the
24649 * element. **Note** : `ngChecked` should not be used alongside `ngModel`.
24650 * Checkout {@link ng.directive:ngChecked ngChecked} for usage.
24651 *
24652 * @example
24653 <example name="range-input-directive" module="rangeExample">
24654 <file name="index.html">
24655 <script>
24656 angular.module('rangeExample', [])
24657 .controller('ExampleController', ['$scope', function($scope) {
24658 $scope.value = 75;
24659 $scope.min = 10;
24660 $scope.max = 90;
24661 }]);
24662 </script>
24663 <form name="myForm" ng-controller="ExampleController">
24664
24665 Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
24666 <hr>
24667 Model as number: <input type="number" ng-model="value"><br>
24668 Min: <input type="number" ng-model="min"><br>
24669 Max: <input type="number" ng-model="max"><br>
24670 value = <code>{{value}}</code><br/>
24671 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
24672 myForm.range.$error = <code>{{myForm.range.$error}}</code>
24673 </form>
24674 </file>
24675 </example>
24676
24677 * ## Range Input with ngMin & ngMax attributes
24678
24679 * @example
24680 <example name="range-input-directive-ng" module="rangeExample">
24681 <file name="index.html">
24682 <script>
24683 angular.module('rangeExample', [])
24684 .controller('ExampleController', ['$scope', function($scope) {
24685 $scope.value = 75;
24686 $scope.min = 10;
24687 $scope.max = 90;
24688 }]);
24689 </script>
24690 <form name="myForm" ng-controller="ExampleController">
24691 Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
24692 <hr>
24693 Model as number: <input type="number" ng-model="value"><br>
24694 Min: <input type="number" ng-model="min"><br>
24695 Max: <input type="number" ng-model="max"><br>
24696 value = <code>{{value}}</code><br/>
24697 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
24698 myForm.range.$error = <code>{{myForm.range.$error}}</code>
24699 </form>
24700 </file>
24701 </example>
24702
24703 */
24704 'range': rangeInputType,
Ed Tanous904063f2017-03-02 16:48:24 -080024705
24706 /**
24707 * @ngdoc input
24708 * @name input[checkbox]
24709 *
24710 * @description
24711 * HTML checkbox.
24712 *
24713 * @param {string} ngModel Assignable angular expression to data-bind to.
24714 * @param {string=} name Property name of the form under which the control is published.
24715 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
24716 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
24717 * @param {string=} ngChange Angular expression to be executed when input changes due to user
24718 * interaction with the input element.
24719 *
24720 * @example
24721 <example name="checkbox-input-directive" module="checkboxExample">
24722 <file name="index.html">
24723 <script>
24724 angular.module('checkboxExample', [])
24725 .controller('ExampleController', ['$scope', function($scope) {
24726 $scope.checkboxModel = {
24727 value1 : true,
24728 value2 : 'YES'
24729 };
24730 }]);
24731 </script>
24732 <form name="myForm" ng-controller="ExampleController">
24733 <label>Value1:
24734 <input type="checkbox" ng-model="checkboxModel.value1">
24735 </label><br/>
24736 <label>Value2:
24737 <input type="checkbox" ng-model="checkboxModel.value2"
24738 ng-true-value="'YES'" ng-false-value="'NO'">
24739 </label><br/>
24740 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
24741 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
24742 </form>
24743 </file>
24744 <file name="protractor.js" type="protractor">
24745 it('should change state', function() {
24746 var value1 = element(by.binding('checkboxModel.value1'));
24747 var value2 = element(by.binding('checkboxModel.value2'));
24748
24749 expect(value1.getText()).toContain('true');
24750 expect(value2.getText()).toContain('YES');
24751
24752 element(by.model('checkboxModel.value1')).click();
24753 element(by.model('checkboxModel.value2')).click();
24754
24755 expect(value1.getText()).toContain('false');
24756 expect(value2.getText()).toContain('NO');
24757 });
24758 </file>
24759 </example>
24760 */
24761 'checkbox': checkboxInputType,
24762
24763 'hidden': noop,
24764 'button': noop,
24765 'submit': noop,
24766 'reset': noop,
24767 'file': noop
24768};
24769
24770function stringBasedInputType(ctrl) {
24771 ctrl.$formatters.push(function(value) {
24772 return ctrl.$isEmpty(value) ? value : value.toString();
24773 });
24774}
24775
24776function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
24777 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
24778 stringBasedInputType(ctrl);
24779}
24780
24781function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
24782 var type = lowercase(element[0].type);
24783
Ed Tanous4758d5b2017-06-06 15:28:13 -070024784 // In composition mode, users are still inputting intermediate text buffer,
Ed Tanous904063f2017-03-02 16:48:24 -080024785 // hold the listener until composition is done.
24786 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
24787 if (!$sniffer.android) {
24788 var composing = false;
24789
24790 element.on('compositionstart', function() {
24791 composing = true;
24792 });
24793
24794 element.on('compositionend', function() {
24795 composing = false;
24796 listener();
24797 });
24798 }
24799
24800 var timeout;
24801
24802 var listener = function(ev) {
24803 if (timeout) {
24804 $browser.defer.cancel(timeout);
24805 timeout = null;
24806 }
24807 if (composing) return;
24808 var value = element.val(),
24809 event = ev && ev.type;
24810
24811 // By default we will trim the value
24812 // If the attribute ng-trim exists we will avoid trimming
24813 // If input type is 'password', the value is never trimmed
24814 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
24815 value = trim(value);
24816 }
24817
24818 // If a control is suffering from bad input (due to native validators), browsers discard its
24819 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
24820 // control's value is the same empty value twice in a row.
24821 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
24822 ctrl.$setViewValue(value, event);
24823 }
24824 };
24825
24826 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
24827 // input event on backspace, delete or cut
24828 if ($sniffer.hasEvent('input')) {
24829 element.on('input', listener);
24830 } else {
24831 var deferListener = function(ev, input, origValue) {
24832 if (!timeout) {
24833 timeout = $browser.defer(function() {
24834 timeout = null;
24835 if (!input || input.value !== origValue) {
24836 listener(ev);
24837 }
24838 });
24839 }
24840 };
24841
Ed Tanous4758d5b2017-06-06 15:28:13 -070024842 element.on('keydown', /** @this */ function(event) {
Ed Tanous904063f2017-03-02 16:48:24 -080024843 var key = event.keyCode;
24844
24845 // ignore
24846 // command modifiers arrows
24847 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
24848
24849 deferListener(event, this, this.value);
24850 });
24851
24852 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
24853 if ($sniffer.hasEvent('paste')) {
24854 element.on('paste cut', deferListener);
24855 }
24856 }
24857
24858 // if user paste into input using mouse on older browser
24859 // or form autocomplete on newer browser, we need "change" event to catch it
24860 element.on('change', listener);
24861
24862 // Some native input types (date-family) have the ability to change validity without
24863 // firing any input/change events.
24864 // For these event types, when native validators are present and the browser supports the type,
24865 // check for validity changes on various DOM events.
24866 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070024867 element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) {
Ed Tanous904063f2017-03-02 16:48:24 -080024868 if (!timeout) {
24869 var validity = this[VALIDITY_STATE_PROPERTY];
24870 var origBadInput = validity.badInput;
24871 var origTypeMismatch = validity.typeMismatch;
24872 timeout = $browser.defer(function() {
24873 timeout = null;
24874 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
24875 listener(ev);
24876 }
24877 });
24878 }
24879 });
24880 }
24881
24882 ctrl.$render = function() {
24883 // Workaround for Firefox validation #12102.
24884 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
24885 if (element.val() !== value) {
24886 element.val(value);
24887 }
24888 };
24889}
24890
24891function weekParser(isoWeek, existingDate) {
24892 if (isDate(isoWeek)) {
24893 return isoWeek;
24894 }
24895
24896 if (isString(isoWeek)) {
24897 WEEK_REGEXP.lastIndex = 0;
24898 var parts = WEEK_REGEXP.exec(isoWeek);
24899 if (parts) {
24900 var year = +parts[1],
24901 week = +parts[2],
24902 hours = 0,
24903 minutes = 0,
24904 seconds = 0,
24905 milliseconds = 0,
24906 firstThurs = getFirstThursdayOfYear(year),
24907 addDays = (week - 1) * 7;
24908
24909 if (existingDate) {
24910 hours = existingDate.getHours();
24911 minutes = existingDate.getMinutes();
24912 seconds = existingDate.getSeconds();
24913 milliseconds = existingDate.getMilliseconds();
24914 }
24915
24916 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
24917 }
24918 }
24919
24920 return NaN;
24921}
24922
24923function createDateParser(regexp, mapping) {
24924 return function(iso, date) {
24925 var parts, map;
24926
24927 if (isDate(iso)) {
24928 return iso;
24929 }
24930
24931 if (isString(iso)) {
24932 // When a date is JSON'ified to wraps itself inside of an extra
24933 // set of double quotes. This makes the date parsing code unable
24934 // to match the date string and parse it as a date.
Ed Tanous4758d5b2017-06-06 15:28:13 -070024935 if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') {
Ed Tanous904063f2017-03-02 16:48:24 -080024936 iso = iso.substring(1, iso.length - 1);
24937 }
24938 if (ISO_DATE_REGEXP.test(iso)) {
24939 return new Date(iso);
24940 }
24941 regexp.lastIndex = 0;
24942 parts = regexp.exec(iso);
24943
24944 if (parts) {
24945 parts.shift();
24946 if (date) {
24947 map = {
24948 yyyy: date.getFullYear(),
24949 MM: date.getMonth() + 1,
24950 dd: date.getDate(),
24951 HH: date.getHours(),
24952 mm: date.getMinutes(),
24953 ss: date.getSeconds(),
24954 sss: date.getMilliseconds() / 1000
24955 };
24956 } else {
24957 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
24958 }
24959
24960 forEach(parts, function(part, index) {
24961 if (index < mapping.length) {
24962 map[mapping[index]] = +part;
24963 }
24964 });
24965 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
24966 }
24967 }
24968
24969 return NaN;
24970 };
24971}
24972
24973function createDateInputType(type, regexp, parseDate, format) {
24974 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
24975 badInputChecker(scope, element, attr, ctrl);
24976 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
Ed Tanous4758d5b2017-06-06 15:28:13 -070024977 var timezone = ctrl && ctrl.$options.getOption('timezone');
Ed Tanous904063f2017-03-02 16:48:24 -080024978 var previousDate;
24979
24980 ctrl.$$parserName = type;
24981 ctrl.$parsers.push(function(value) {
24982 if (ctrl.$isEmpty(value)) return null;
24983 if (regexp.test(value)) {
24984 // Note: We cannot read ctrl.$modelValue, as there might be a different
24985 // parser/formatter in the processing chain so that the model
24986 // contains some different data format!
24987 var parsedDate = parseDate(value, previousDate);
24988 if (timezone) {
24989 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
24990 }
24991 return parsedDate;
24992 }
24993 return undefined;
24994 });
24995
24996 ctrl.$formatters.push(function(value) {
24997 if (value && !isDate(value)) {
24998 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
24999 }
25000 if (isValidDate(value)) {
25001 previousDate = value;
25002 if (previousDate && timezone) {
25003 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
25004 }
25005 return $filter('date')(value, format, timezone);
25006 } else {
25007 previousDate = null;
25008 return '';
25009 }
25010 });
25011
25012 if (isDefined(attr.min) || attr.ngMin) {
25013 var minVal;
25014 ctrl.$validators.min = function(value) {
25015 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
25016 };
25017 attr.$observe('min', function(val) {
25018 minVal = parseObservedDateValue(val);
25019 ctrl.$validate();
25020 });
25021 }
25022
25023 if (isDefined(attr.max) || attr.ngMax) {
25024 var maxVal;
25025 ctrl.$validators.max = function(value) {
25026 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
25027 };
25028 attr.$observe('max', function(val) {
25029 maxVal = parseObservedDateValue(val);
25030 ctrl.$validate();
25031 });
25032 }
25033
25034 function isValidDate(value) {
25035 // Invalid Date: getTime() returns NaN
25036 return value && !(value.getTime && value.getTime() !== value.getTime());
25037 }
25038
25039 function parseObservedDateValue(val) {
25040 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
25041 }
25042 };
25043}
25044
25045function badInputChecker(scope, element, attr, ctrl) {
25046 var node = element[0];
25047 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
25048 if (nativeValidation) {
25049 ctrl.$parsers.push(function(value) {
25050 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
25051 return validity.badInput || validity.typeMismatch ? undefined : value;
25052 });
25053 }
25054}
25055
Ed Tanous4758d5b2017-06-06 15:28:13 -070025056function numberFormatterParser(ctrl) {
Ed Tanous904063f2017-03-02 16:48:24 -080025057 ctrl.$$parserName = 'number';
25058 ctrl.$parsers.push(function(value) {
25059 if (ctrl.$isEmpty(value)) return null;
25060 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
25061 return undefined;
25062 });
25063
25064 ctrl.$formatters.push(function(value) {
25065 if (!ctrl.$isEmpty(value)) {
25066 if (!isNumber(value)) {
25067 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
25068 }
25069 value = value.toString();
25070 }
25071 return value;
25072 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070025073}
25074
25075function parseNumberAttrVal(val) {
25076 if (isDefined(val) && !isNumber(val)) {
25077 val = parseFloat(val);
25078 }
25079 return !isNumberNaN(val) ? val : undefined;
25080}
25081
25082function isNumberInteger(num) {
25083 // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066
25084 // (minus the assumption that `num` is a number)
25085
25086 // eslint-disable-next-line no-bitwise
25087 return (num | 0) === num;
25088}
25089
25090function countDecimals(num) {
25091 var numString = num.toString();
25092 var decimalSymbolIndex = numString.indexOf('.');
25093
25094 if (decimalSymbolIndex === -1) {
25095 if (-1 < num && num < 1) {
25096 // It may be in the exponential notation format (`1e-X`)
25097 var match = /e-(\d+)$/.exec(numString);
25098
25099 if (match) {
25100 return Number(match[1]);
25101 }
25102 }
25103
25104 return 0;
25105 }
25106
25107 return numString.length - decimalSymbolIndex - 1;
25108}
25109
25110function isValidForStep(viewValue, stepBase, step) {
25111 // At this point `stepBase` and `step` are expected to be non-NaN values
25112 // and `viewValue` is expected to be a valid stringified number.
25113 var value = Number(viewValue);
25114
25115 var isNonIntegerValue = !isNumberInteger(value);
25116 var isNonIntegerStepBase = !isNumberInteger(stepBase);
25117 var isNonIntegerStep = !isNumberInteger(step);
25118
25119 // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
25120 // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
25121 if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
25122 var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
25123 var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
25124 var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;
25125
25126 var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals);
25127 var multiplier = Math.pow(10, decimalCount);
25128
25129 value = value * multiplier;
25130 stepBase = stepBase * multiplier;
25131 step = step * multiplier;
25132
25133 if (isNonIntegerValue) value = Math.round(value);
25134 if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
25135 if (isNonIntegerStep) step = Math.round(step);
25136 }
25137
25138 return (value - stepBase) % step === 0;
25139}
25140
25141function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25142 badInputChecker(scope, element, attr, ctrl);
25143 numberFormatterParser(ctrl);
25144 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25145
25146 var minVal;
25147 var maxVal;
Ed Tanous904063f2017-03-02 16:48:24 -080025148
25149 if (isDefined(attr.min) || attr.ngMin) {
Ed Tanous904063f2017-03-02 16:48:24 -080025150 ctrl.$validators.min = function(value) {
25151 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
25152 };
25153
25154 attr.$observe('min', function(val) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025155 minVal = parseNumberAttrVal(val);
Ed Tanous904063f2017-03-02 16:48:24 -080025156 // TODO(matsko): implement validateLater to reduce number of validations
25157 ctrl.$validate();
25158 });
25159 }
25160
25161 if (isDefined(attr.max) || attr.ngMax) {
Ed Tanous904063f2017-03-02 16:48:24 -080025162 ctrl.$validators.max = function(value) {
25163 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
25164 };
25165
25166 attr.$observe('max', function(val) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025167 maxVal = parseNumberAttrVal(val);
Ed Tanous904063f2017-03-02 16:48:24 -080025168 // TODO(matsko): implement validateLater to reduce number of validations
25169 ctrl.$validate();
25170 });
25171 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070025172
25173 if (isDefined(attr.step) || attr.ngStep) {
25174 var stepVal;
25175 ctrl.$validators.step = function(modelValue, viewValue) {
25176 return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
25177 isValidForStep(viewValue, minVal || 0, stepVal);
25178 };
25179
25180 attr.$observe('step', function(val) {
25181 stepVal = parseNumberAttrVal(val);
25182 // TODO(matsko): implement validateLater to reduce number of validations
25183 ctrl.$validate();
25184 });
25185 }
25186}
25187
25188function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25189 badInputChecker(scope, element, attr, ctrl);
25190 numberFormatterParser(ctrl);
25191 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25192
25193 var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
25194 minVal = supportsRange ? 0 : undefined,
25195 maxVal = supportsRange ? 100 : undefined,
25196 stepVal = supportsRange ? 1 : undefined,
25197 validity = element[0].validity,
25198 hasMinAttr = isDefined(attr.min),
25199 hasMaxAttr = isDefined(attr.max),
25200 hasStepAttr = isDefined(attr.step);
25201
25202 var originalRender = ctrl.$render;
25203
25204 ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ?
25205 //Browsers that implement range will set these values automatically, but reading the adjusted values after
25206 //$render would cause the min / max validators to be applied with the wrong value
25207 function rangeRender() {
25208 originalRender();
25209 ctrl.$setViewValue(element.val());
25210 } :
25211 originalRender;
25212
25213 if (hasMinAttr) {
25214 ctrl.$validators.min = supportsRange ?
25215 // Since all browsers set the input to a valid value, we don't need to check validity
25216 function noopMinValidator() { return true; } :
25217 // non-support browsers validate the min val
25218 function minValidator(modelValue, viewValue) {
25219 return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
25220 };
25221
25222 setInitialValueAndObserver('min', minChange);
25223 }
25224
25225 if (hasMaxAttr) {
25226 ctrl.$validators.max = supportsRange ?
25227 // Since all browsers set the input to a valid value, we don't need to check validity
25228 function noopMaxValidator() { return true; } :
25229 // non-support browsers validate the max val
25230 function maxValidator(modelValue, viewValue) {
25231 return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
25232 };
25233
25234 setInitialValueAndObserver('max', maxChange);
25235 }
25236
25237 if (hasStepAttr) {
25238 ctrl.$validators.step = supportsRange ?
25239 function nativeStepValidator() {
25240 // Currently, only FF implements the spec on step change correctly (i.e. adjusting the
25241 // input element value to a valid value). It's possible that other browsers set the stepMismatch
25242 // validity error instead, so we can at least report an error in that case.
25243 return !validity.stepMismatch;
25244 } :
25245 // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
25246 function stepValidator(modelValue, viewValue) {
25247 return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
25248 isValidForStep(viewValue, minVal || 0, stepVal);
25249 };
25250
25251 setInitialValueAndObserver('step', stepChange);
25252 }
25253
25254 function setInitialValueAndObserver(htmlAttrName, changeFn) {
25255 // interpolated attributes set the attribute value only after a digest, but we need the
25256 // attribute value when the input is first rendered, so that the browser can adjust the
25257 // input value based on the min/max value
25258 element.attr(htmlAttrName, attr[htmlAttrName]);
25259 attr.$observe(htmlAttrName, changeFn);
25260 }
25261
25262 function minChange(val) {
25263 minVal = parseNumberAttrVal(val);
25264 // ignore changes before model is initialized
25265 if (isNumberNaN(ctrl.$modelValue)) {
25266 return;
25267 }
25268
25269 if (supportsRange) {
25270 var elVal = element.val();
25271 // IE11 doesn't set the el val correctly if the minVal is greater than the element value
25272 if (minVal > elVal) {
25273 elVal = minVal;
25274 element.val(elVal);
25275 }
25276 ctrl.$setViewValue(elVal);
25277 } else {
25278 // TODO(matsko): implement validateLater to reduce number of validations
25279 ctrl.$validate();
25280 }
25281 }
25282
25283 function maxChange(val) {
25284 maxVal = parseNumberAttrVal(val);
25285 // ignore changes before model is initialized
25286 if (isNumberNaN(ctrl.$modelValue)) {
25287 return;
25288 }
25289
25290 if (supportsRange) {
25291 var elVal = element.val();
25292 // IE11 doesn't set the el val correctly if the maxVal is less than the element value
25293 if (maxVal < elVal) {
25294 element.val(maxVal);
25295 // IE11 and Chrome don't set the value to the minVal when max < min
25296 elVal = maxVal < minVal ? minVal : maxVal;
25297 }
25298 ctrl.$setViewValue(elVal);
25299 } else {
25300 // TODO(matsko): implement validateLater to reduce number of validations
25301 ctrl.$validate();
25302 }
25303 }
25304
25305 function stepChange(val) {
25306 stepVal = parseNumberAttrVal(val);
25307 // ignore changes before model is initialized
25308 if (isNumberNaN(ctrl.$modelValue)) {
25309 return;
25310 }
25311
25312 // Some browsers don't adjust the input value correctly, but set the stepMismatch error
25313 if (supportsRange && ctrl.$viewValue !== element.val()) {
25314 ctrl.$setViewValue(element.val());
25315 } else {
25316 // TODO(matsko): implement validateLater to reduce number of validations
25317 ctrl.$validate();
25318 }
25319 }
Ed Tanous904063f2017-03-02 16:48:24 -080025320}
25321
25322function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25323 // Note: no badInputChecker here by purpose as `url` is only a validation
25324 // in browsers, i.e. we can always read out input.value even if it is not valid!
25325 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25326 stringBasedInputType(ctrl);
25327
25328 ctrl.$$parserName = 'url';
25329 ctrl.$validators.url = function(modelValue, viewValue) {
25330 var value = modelValue || viewValue;
25331 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
25332 };
25333}
25334
25335function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25336 // Note: no badInputChecker here by purpose as `url` is only a validation
25337 // in browsers, i.e. we can always read out input.value even if it is not valid!
25338 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25339 stringBasedInputType(ctrl);
25340
25341 ctrl.$$parserName = 'email';
25342 ctrl.$validators.email = function(modelValue, viewValue) {
25343 var value = modelValue || viewValue;
25344 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
25345 };
25346}
25347
25348function radioInputType(scope, element, attr, ctrl) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025349 var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false';
Ed Tanous904063f2017-03-02 16:48:24 -080025350 // make the name unique, if not defined
25351 if (isUndefined(attr.name)) {
25352 element.attr('name', nextUid());
25353 }
25354
25355 var listener = function(ev) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025356 var value;
Ed Tanous904063f2017-03-02 16:48:24 -080025357 if (element[0].checked) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025358 value = attr.value;
25359 if (doTrim) {
25360 value = trim(value);
25361 }
25362 ctrl.$setViewValue(value, ev && ev.type);
Ed Tanous904063f2017-03-02 16:48:24 -080025363 }
25364 };
25365
25366 element.on('click', listener);
25367
25368 ctrl.$render = function() {
25369 var value = attr.value;
Ed Tanous4758d5b2017-06-06 15:28:13 -070025370 if (doTrim) {
25371 value = trim(value);
25372 }
25373 element[0].checked = (value === ctrl.$viewValue);
Ed Tanous904063f2017-03-02 16:48:24 -080025374 };
25375
25376 attr.$observe('value', ctrl.$render);
25377}
25378
25379function parseConstantExpr($parse, context, name, expression, fallback) {
25380 var parseFn;
25381 if (isDefined(expression)) {
25382 parseFn = $parse(expression);
25383 if (!parseFn.constant) {
25384 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
25385 '`{1}`.', name, expression);
25386 }
25387 return parseFn(context);
25388 }
25389 return fallback;
25390}
25391
25392function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
25393 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
25394 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
25395
25396 var listener = function(ev) {
25397 ctrl.$setViewValue(element[0].checked, ev && ev.type);
25398 };
25399
25400 element.on('click', listener);
25401
25402 ctrl.$render = function() {
25403 element[0].checked = ctrl.$viewValue;
25404 };
25405
25406 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
25407 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
25408 // it to a boolean.
25409 ctrl.$isEmpty = function(value) {
25410 return value === false;
25411 };
25412
25413 ctrl.$formatters.push(function(value) {
25414 return equals(value, trueValue);
25415 });
25416
25417 ctrl.$parsers.push(function(value) {
25418 return value ? trueValue : falseValue;
25419 });
25420}
25421
25422
25423/**
25424 * @ngdoc directive
25425 * @name textarea
25426 * @restrict E
25427 *
25428 * @description
25429 * HTML textarea element control with angular data-binding. The data-binding and validation
25430 * properties of this element are exactly the same as those of the
25431 * {@link ng.directive:input input element}.
25432 *
25433 * @param {string} ngModel Assignable angular expression to data-bind to.
25434 * @param {string=} name Property name of the form under which the control is published.
25435 * @param {string=} required Sets `required` validation error key if the value is not entered.
25436 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25437 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25438 * `required` when you want to data-bind to the `required` attribute.
25439 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
25440 * minlength.
25441 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
25442 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
25443 * length.
25444 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
25445 * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
25446 * If the expression evaluates to a RegExp object, then this is used directly.
25447 * If the expression evaluates to a string, then it will be converted to a RegExp
25448 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
25449 * `new RegExp('^abc$')`.<br />
25450 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
25451 * start at the index of the last search's match, thus not taking the whole input value into
25452 * account.
25453 * @param {string=} ngChange Angular expression to be executed when input changes due to user
25454 * interaction with the input element.
25455 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
Ed Tanous4758d5b2017-06-06 15:28:13 -070025456 *
25457 * @knownIssue
25458 *
25459 * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily
25460 * insert the placeholder value as the textarea's content. If the placeholder value contains
25461 * interpolation (`{{ ... }}`), an error will be logged in the console when Angular tries to update
25462 * the value of the by-then-removed text node. This doesn't affect the functionality of the
25463 * textarea, but can be undesirable.
25464 *
25465 * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of
25466 * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can
25467 * find more details on `ngAttr` in the
25468 * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the
25469 * Developer Guide.
Ed Tanous904063f2017-03-02 16:48:24 -080025470 */
25471
25472
25473/**
25474 * @ngdoc directive
25475 * @name input
25476 * @restrict E
25477 *
25478 * @description
25479 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
25480 * input state control, and validation.
25481 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
25482 *
25483 * <div class="alert alert-warning">
25484 * **Note:** Not every feature offered is available for all input types.
25485 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
25486 * </div>
25487 *
25488 * @param {string} ngModel Assignable angular expression to data-bind to.
25489 * @param {string=} name Property name of the form under which the control is published.
25490 * @param {string=} required Sets `required` validation error key if the value is not entered.
25491 * @param {boolean=} ngRequired Sets `required` attribute if set to true
25492 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
25493 * minlength.
25494 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
25495 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
25496 * length.
25497 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
25498 * value does not match a RegExp found by evaluating the Angular expression given in the attribute value.
25499 * If the expression evaluates to a RegExp object, then this is used directly.
25500 * If the expression evaluates to a string, then it will be converted to a RegExp
25501 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
25502 * `new RegExp('^abc$')`.<br />
25503 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
25504 * start at the index of the last search's match, thus not taking the whole input value into
25505 * account.
25506 * @param {string=} ngChange Angular expression to be executed when input changes due to user
25507 * interaction with the input element.
25508 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
25509 * This parameter is ignored for input[type=password] controls, which will never trim the
25510 * input.
25511 *
25512 * @example
25513 <example name="input-directive" module="inputExample">
25514 <file name="index.html">
25515 <script>
25516 angular.module('inputExample', [])
25517 .controller('ExampleController', ['$scope', function($scope) {
25518 $scope.user = {name: 'guest', last: 'visitor'};
25519 }]);
25520 </script>
25521 <div ng-controller="ExampleController">
25522 <form name="myForm">
25523 <label>
25524 User name:
25525 <input type="text" name="userName" ng-model="user.name" required>
25526 </label>
25527 <div role="alert">
25528 <span class="error" ng-show="myForm.userName.$error.required">
25529 Required!</span>
25530 </div>
25531 <label>
25532 Last name:
25533 <input type="text" name="lastName" ng-model="user.last"
25534 ng-minlength="3" ng-maxlength="10">
25535 </label>
25536 <div role="alert">
25537 <span class="error" ng-show="myForm.lastName.$error.minlength">
25538 Too short!</span>
25539 <span class="error" ng-show="myForm.lastName.$error.maxlength">
25540 Too long!</span>
25541 </div>
25542 </form>
25543 <hr>
25544 <tt>user = {{user}}</tt><br/>
25545 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
25546 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
25547 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
25548 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
25549 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25550 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25551 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
25552 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
25553 </div>
25554 </file>
25555 <file name="protractor.js" type="protractor">
25556 var user = element(by.exactBinding('user'));
25557 var userNameValid = element(by.binding('myForm.userName.$valid'));
25558 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
25559 var lastNameError = element(by.binding('myForm.lastName.$error'));
25560 var formValid = element(by.binding('myForm.$valid'));
25561 var userNameInput = element(by.model('user.name'));
25562 var userLastInput = element(by.model('user.last'));
25563
25564 it('should initialize to model', function() {
25565 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
25566 expect(userNameValid.getText()).toContain('true');
25567 expect(formValid.getText()).toContain('true');
25568 });
25569
25570 it('should be invalid if empty when required', function() {
25571 userNameInput.clear();
25572 userNameInput.sendKeys('');
25573
25574 expect(user.getText()).toContain('{"last":"visitor"}');
25575 expect(userNameValid.getText()).toContain('false');
25576 expect(formValid.getText()).toContain('false');
25577 });
25578
25579 it('should be valid if empty when min length is set', function() {
25580 userLastInput.clear();
25581 userLastInput.sendKeys('');
25582
25583 expect(user.getText()).toContain('{"name":"guest","last":""}');
25584 expect(lastNameValid.getText()).toContain('true');
25585 expect(formValid.getText()).toContain('true');
25586 });
25587
25588 it('should be invalid if less than required min length', function() {
25589 userLastInput.clear();
25590 userLastInput.sendKeys('xx');
25591
25592 expect(user.getText()).toContain('{"name":"guest"}');
25593 expect(lastNameValid.getText()).toContain('false');
25594 expect(lastNameError.getText()).toContain('minlength');
25595 expect(formValid.getText()).toContain('false');
25596 });
25597
25598 it('should be invalid if longer than max length', function() {
25599 userLastInput.clear();
25600 userLastInput.sendKeys('some ridiculously long name');
25601
25602 expect(user.getText()).toContain('{"name":"guest"}');
25603 expect(lastNameValid.getText()).toContain('false');
25604 expect(lastNameError.getText()).toContain('maxlength');
25605 expect(formValid.getText()).toContain('false');
25606 });
25607 </file>
25608 </example>
25609 */
25610var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
25611 function($browser, $sniffer, $filter, $parse) {
25612 return {
25613 restrict: 'E',
25614 require: ['?ngModel'],
25615 link: {
25616 pre: function(scope, element, attr, ctrls) {
25617 if (ctrls[0]) {
25618 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
25619 $browser, $filter, $parse);
25620 }
25621 }
25622 }
25623 };
25624}];
25625
25626
25627
25628var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
25629/**
25630 * @ngdoc directive
25631 * @name ngValue
25632 *
25633 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070025634 * Binds the given expression to the value of the element.
Ed Tanous904063f2017-03-02 16:48:24 -080025635 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070025636 * It is mainly used on {@link input[radio] `input[radio]`} and option elements,
25637 * so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its
25638 * {@link select `select`} parent element) is set to the bound value. It is especially useful
25639 * for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below.
Ed Tanous904063f2017-03-02 16:48:24 -080025640 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070025641 * It can also be used to achieve one-way binding of a given expression to an input element
25642 * such as an `input[text]` or a `textarea`, when that element does not use ngModel.
Ed Tanous904063f2017-03-02 16:48:24 -080025643 *
25644 * @element input
25645 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
Ed Tanous4758d5b2017-06-06 15:28:13 -070025646 * and `value` property of the element.
Ed Tanous904063f2017-03-02 16:48:24 -080025647 *
25648 * @example
25649 <example name="ngValue-directive" module="valueExample">
25650 <file name="index.html">
25651 <script>
25652 angular.module('valueExample', [])
25653 .controller('ExampleController', ['$scope', function($scope) {
25654 $scope.names = ['pizza', 'unicorns', 'robots'];
25655 $scope.my = { favorite: 'unicorns' };
25656 }]);
25657 </script>
25658 <form ng-controller="ExampleController">
25659 <h2>Which is your favorite?</h2>
25660 <label ng-repeat="name in names" for="{{name}}">
25661 {{name}}
25662 <input type="radio"
25663 ng-model="my.favorite"
25664 ng-value="name"
25665 id="{{name}}"
25666 name="favorite">
25667 </label>
25668 <div>You chose {{my.favorite}}</div>
25669 </form>
25670 </file>
25671 <file name="protractor.js" type="protractor">
25672 var favorite = element(by.binding('my.favorite'));
25673
25674 it('should initialize to model', function() {
25675 expect(favorite.getText()).toContain('unicorns');
25676 });
25677 it('should bind the values to the inputs', function() {
25678 element.all(by.model('my.favorite')).get(0).click();
25679 expect(favorite.getText()).toContain('pizza');
25680 });
25681 </file>
25682 </example>
25683 */
25684var ngValueDirective = function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025685 /**
25686 * inputs use the value attribute as their default value if the value property is not set.
25687 * Once the value property has been set (by adding input), it will not react to changes to
25688 * the value attribute anymore. Setting both attribute and property fixes this behavior, and
25689 * makes it possible to use ngValue as a sort of one-way bind.
25690 */
25691 function updateElementValue(element, attr, value) {
25692 // Support: IE9 only
25693 // In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
25694 var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
25695 element.prop('value', propValue);
25696 attr.$set('value', value);
25697 }
25698
Ed Tanous904063f2017-03-02 16:48:24 -080025699 return {
25700 restrict: 'A',
25701 priority: 100,
25702 compile: function(tpl, tplAttr) {
25703 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
25704 return function ngValueConstantLink(scope, elm, attr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025705 var value = scope.$eval(attr.ngValue);
25706 updateElementValue(elm, attr, value);
Ed Tanous904063f2017-03-02 16:48:24 -080025707 };
25708 } else {
25709 return function ngValueLink(scope, elm, attr) {
25710 scope.$watch(attr.ngValue, function valueWatchAction(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025711 updateElementValue(elm, attr, value);
Ed Tanous904063f2017-03-02 16:48:24 -080025712 });
25713 };
25714 }
25715 }
25716 };
25717};
25718
25719/**
25720 * @ngdoc directive
25721 * @name ngBind
25722 * @restrict AC
25723 *
25724 * @description
25725 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
25726 * with the value of a given expression, and to update the text content when the value of that
25727 * expression changes.
25728 *
25729 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
25730 * `{{ expression }}` which is similar but less verbose.
25731 *
25732 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
25733 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
25734 * element attribute, it makes the bindings invisible to the user while the page is loading.
25735 *
25736 * An alternative solution to this problem would be using the
25737 * {@link ng.directive:ngCloak ngCloak} directive.
25738 *
25739 *
25740 * @element ANY
25741 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
25742 *
25743 * @example
25744 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
Ed Tanous4758d5b2017-06-06 15:28:13 -070025745 <example module="bindExample" name="ng-bind">
Ed Tanous904063f2017-03-02 16:48:24 -080025746 <file name="index.html">
25747 <script>
25748 angular.module('bindExample', [])
25749 .controller('ExampleController', ['$scope', function($scope) {
25750 $scope.name = 'Whirled';
25751 }]);
25752 </script>
25753 <div ng-controller="ExampleController">
25754 <label>Enter name: <input type="text" ng-model="name"></label><br>
25755 Hello <span ng-bind="name"></span>!
25756 </div>
25757 </file>
25758 <file name="protractor.js" type="protractor">
25759 it('should check ng-bind', function() {
25760 var nameInput = element(by.model('name'));
25761
25762 expect(element(by.binding('name')).getText()).toBe('Whirled');
25763 nameInput.clear();
25764 nameInput.sendKeys('world');
25765 expect(element(by.binding('name')).getText()).toBe('world');
25766 });
25767 </file>
25768 </example>
25769 */
25770var ngBindDirective = ['$compile', function($compile) {
25771 return {
25772 restrict: 'AC',
25773 compile: function ngBindCompile(templateElement) {
25774 $compile.$$addBindingClass(templateElement);
25775 return function ngBindLink(scope, element, attr) {
25776 $compile.$$addBindingInfo(element, attr.ngBind);
25777 element = element[0];
25778 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070025779 element.textContent = stringify(value);
Ed Tanous904063f2017-03-02 16:48:24 -080025780 });
25781 };
25782 }
25783 };
25784}];
25785
25786
25787/**
25788 * @ngdoc directive
25789 * @name ngBindTemplate
25790 *
25791 * @description
25792 * The `ngBindTemplate` directive specifies that the element
25793 * text content should be replaced with the interpolation of the template
25794 * in the `ngBindTemplate` attribute.
25795 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
25796 * expressions. This directive is needed since some HTML elements
25797 * (such as TITLE and OPTION) cannot contain SPAN elements.
25798 *
25799 * @element ANY
25800 * @param {string} ngBindTemplate template of form
25801 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
25802 *
25803 * @example
25804 * Try it here: enter text in text box and watch the greeting change.
Ed Tanous4758d5b2017-06-06 15:28:13 -070025805 <example module="bindExample" name="ng-bind-template">
Ed Tanous904063f2017-03-02 16:48:24 -080025806 <file name="index.html">
25807 <script>
25808 angular.module('bindExample', [])
25809 .controller('ExampleController', ['$scope', function($scope) {
25810 $scope.salutation = 'Hello';
25811 $scope.name = 'World';
25812 }]);
25813 </script>
25814 <div ng-controller="ExampleController">
25815 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
25816 <label>Name: <input type="text" ng-model="name"></label><br>
25817 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
25818 </div>
25819 </file>
25820 <file name="protractor.js" type="protractor">
25821 it('should check ng-bind', function() {
25822 var salutationElem = element(by.binding('salutation'));
25823 var salutationInput = element(by.model('salutation'));
25824 var nameInput = element(by.model('name'));
25825
25826 expect(salutationElem.getText()).toBe('Hello World!');
25827
25828 salutationInput.clear();
25829 salutationInput.sendKeys('Greetings');
25830 nameInput.clear();
25831 nameInput.sendKeys('user');
25832
25833 expect(salutationElem.getText()).toBe('Greetings user!');
25834 });
25835 </file>
25836 </example>
25837 */
25838var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
25839 return {
25840 compile: function ngBindTemplateCompile(templateElement) {
25841 $compile.$$addBindingClass(templateElement);
25842 return function ngBindTemplateLink(scope, element, attr) {
25843 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
25844 $compile.$$addBindingInfo(element, interpolateFn.expressions);
25845 element = element[0];
25846 attr.$observe('ngBindTemplate', function(value) {
25847 element.textContent = isUndefined(value) ? '' : value;
25848 });
25849 };
25850 }
25851 };
25852}];
25853
25854
25855/**
25856 * @ngdoc directive
25857 * @name ngBindHtml
25858 *
25859 * @description
25860 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
25861 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
25862 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
25863 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
25864 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
25865 *
25866 * You may also bypass sanitization for values you know are safe. To do so, bind to
25867 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
25868 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
25869 *
25870 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
25871 * will have an exception (instead of an exploit.)
25872 *
25873 * @element ANY
25874 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
25875 *
25876 * @example
25877
Ed Tanous4758d5b2017-06-06 15:28:13 -070025878 <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html">
Ed Tanous904063f2017-03-02 16:48:24 -080025879 <file name="index.html">
25880 <div ng-controller="ExampleController">
25881 <p ng-bind-html="myHTML"></p>
25882 </div>
25883 </file>
25884
25885 <file name="script.js">
25886 angular.module('bindHtmlExample', ['ngSanitize'])
25887 .controller('ExampleController', ['$scope', function($scope) {
25888 $scope.myHTML =
25889 'I am an <code>HTML</code>string with ' +
25890 '<a href="#">links!</a> and other <em>stuff</em>';
25891 }]);
25892 </file>
25893
25894 <file name="protractor.js" type="protractor">
25895 it('should check ng-bind-html', function() {
25896 expect(element(by.binding('myHTML')).getText()).toBe(
25897 'I am an HTMLstring with links! and other stuff');
25898 });
25899 </file>
25900 </example>
25901 */
25902var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
25903 return {
25904 restrict: 'A',
25905 compile: function ngBindHtmlCompile(tElement, tAttrs) {
25906 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
25907 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
25908 // Unwrap the value to compare the actual inner safe value, not the wrapper object.
25909 return $sce.valueOf(val);
25910 });
25911 $compile.$$addBindingClass(tElement);
25912
25913 return function ngBindHtmlLink(scope, element, attr) {
25914 $compile.$$addBindingInfo(element, attr.ngBindHtml);
25915
25916 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
25917 // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
25918 var value = ngBindHtmlGetter(scope);
25919 element.html($sce.getTrustedHtml(value) || '');
25920 });
25921 };
25922 }
25923 };
25924}];
25925
25926/**
25927 * @ngdoc directive
25928 * @name ngChange
25929 *
25930 * @description
25931 * Evaluate the given expression when the user changes the input.
25932 * The expression is evaluated immediately, unlike the JavaScript onchange event
25933 * which only triggers at the end of a change (usually, when the user leaves the
25934 * form element or presses the return key).
25935 *
25936 * The `ngChange` expression is only evaluated when a change in the input value causes
25937 * a new value to be committed to the model.
25938 *
25939 * It will not be evaluated:
25940 * * if the value returned from the `$parsers` transformation pipeline has not changed
25941 * * if the input has continued to be invalid since the model will stay `null`
25942 * * if the model is changed programmatically and not by a change to the input value
25943 *
25944 *
25945 * Note, this directive requires `ngModel` to be present.
25946 *
25947 * @element input
25948 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
25949 * in input value.
25950 *
25951 * @example
25952 * <example name="ngChange-directive" module="changeExample">
25953 * <file name="index.html">
25954 * <script>
25955 * angular.module('changeExample', [])
25956 * .controller('ExampleController', ['$scope', function($scope) {
25957 * $scope.counter = 0;
25958 * $scope.change = function() {
25959 * $scope.counter++;
25960 * };
25961 * }]);
25962 * </script>
25963 * <div ng-controller="ExampleController">
25964 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
25965 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
25966 * <label for="ng-change-example2">Confirmed</label><br />
25967 * <tt>debug = {{confirmed}}</tt><br/>
25968 * <tt>counter = {{counter}}</tt><br/>
25969 * </div>
25970 * </file>
25971 * <file name="protractor.js" type="protractor">
25972 * var counter = element(by.binding('counter'));
25973 * var debug = element(by.binding('confirmed'));
25974 *
25975 * it('should evaluate the expression if changing from view', function() {
25976 * expect(counter.getText()).toContain('0');
25977 *
25978 * element(by.id('ng-change-example1')).click();
25979 *
25980 * expect(counter.getText()).toContain('1');
25981 * expect(debug.getText()).toContain('true');
25982 * });
25983 *
25984 * it('should not evaluate the expression if changing from model', function() {
25985 * element(by.id('ng-change-example2')).click();
25986
25987 * expect(counter.getText()).toContain('0');
25988 * expect(debug.getText()).toContain('true');
25989 * });
25990 * </file>
25991 * </example>
25992 */
25993var ngChangeDirective = valueFn({
25994 restrict: 'A',
25995 require: 'ngModel',
25996 link: function(scope, element, attr, ctrl) {
25997 ctrl.$viewChangeListeners.push(function() {
25998 scope.$eval(attr.ngChange);
25999 });
26000 }
26001});
26002
Ed Tanous4758d5b2017-06-06 15:28:13 -070026003/* exported
26004 ngClassDirective,
26005 ngClassEvenDirective,
26006 ngClassOddDirective
26007*/
26008
Ed Tanous904063f2017-03-02 16:48:24 -080026009function classDirective(name, selector) {
26010 name = 'ngClass' + name;
Ed Tanous4758d5b2017-06-06 15:28:13 -070026011 var indexWatchExpression;
26012
26013 return ['$parse', function($parse) {
Ed Tanous904063f2017-03-02 16:48:24 -080026014 return {
26015 restrict: 'AC',
26016 link: function(scope, element, attr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070026017 var classCounts = element.data('$classCounts');
26018 var oldModulo = true;
26019 var oldClassString;
Ed Tanous904063f2017-03-02 16:48:24 -080026020
Ed Tanous4758d5b2017-06-06 15:28:13 -070026021 if (!classCounts) {
Ed Tanous904063f2017-03-02 16:48:24 -080026022 // Use createMap() to prevent class assumptions involving property
26023 // names in Object.prototype
Ed Tanous4758d5b2017-06-06 15:28:13 -070026024 classCounts = createMap();
26025 element.data('$classCounts', classCounts);
26026 }
26027
26028 if (name !== 'ngClass') {
26029 if (!indexWatchExpression) {
26030 indexWatchExpression = $parse('$index', function moduloTwo($index) {
26031 // eslint-disable-next-line no-bitwise
26032 return $index & 1;
26033 });
26034 }
26035
26036 scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
26037 }
26038
26039 scope.$watch($parse(attr[name], toClassString), ngClassWatchAction);
26040
26041 function addClasses(classString) {
26042 classString = digestClassCounts(split(classString), 1);
26043 attr.$addClass(classString);
26044 }
26045
26046 function removeClasses(classString) {
26047 classString = digestClassCounts(split(classString), -1);
26048 attr.$removeClass(classString);
26049 }
26050
26051 function updateClasses(oldClassString, newClassString) {
26052 var oldClassArray = split(oldClassString);
26053 var newClassArray = split(newClassString);
26054
26055 var toRemoveArray = arrayDifference(oldClassArray, newClassArray);
26056 var toAddArray = arrayDifference(newClassArray, oldClassArray);
26057
26058 var toRemoveString = digestClassCounts(toRemoveArray, -1);
26059 var toAddString = digestClassCounts(toAddArray, 1);
26060
26061 attr.$addClass(toAddString);
26062 attr.$removeClass(toRemoveString);
26063 }
26064
26065 function digestClassCounts(classArray, count) {
Ed Tanous904063f2017-03-02 16:48:24 -080026066 var classesToUpdate = [];
Ed Tanous4758d5b2017-06-06 15:28:13 -070026067
26068 forEach(classArray, function(className) {
Ed Tanous904063f2017-03-02 16:48:24 -080026069 if (count > 0 || classCounts[className]) {
26070 classCounts[className] = (classCounts[className] || 0) + count;
26071 if (classCounts[className] === +(count > 0)) {
26072 classesToUpdate.push(className);
26073 }
26074 }
26075 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070026076
Ed Tanous904063f2017-03-02 16:48:24 -080026077 return classesToUpdate.join(' ');
26078 }
26079
Ed Tanous4758d5b2017-06-06 15:28:13 -070026080 function ngClassIndexWatchAction(newModulo) {
26081 // This watch-action should run before the `ngClassWatchAction()`, thus it
26082 // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
26083 // `ngClassWatchAction()` will update the classes.
26084 if (newModulo === selector) {
26085 addClasses(oldClassString);
26086 } else {
26087 removeClasses(oldClassString);
Ed Tanous904063f2017-03-02 16:48:24 -080026088 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070026089
26090 oldModulo = newModulo;
Ed Tanous904063f2017-03-02 16:48:24 -080026091 }
26092
Ed Tanous4758d5b2017-06-06 15:28:13 -070026093 function ngClassWatchAction(newClassString) {
26094 // When using a one-time binding the newClassString will return
26095 // the pre-interceptor value until the one-time is complete
26096 if (!isString(newClassString)) {
26097 newClassString = toClassString(newClassString);
Ed Tanous904063f2017-03-02 16:48:24 -080026098 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070026099
26100 if (oldModulo === selector) {
26101 updateClasses(oldClassString, newClassString);
Ed Tanous904063f2017-03-02 16:48:24 -080026102 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070026103
26104 oldClassString = newClassString;
Ed Tanous904063f2017-03-02 16:48:24 -080026105 }
26106 }
26107 };
Ed Tanous904063f2017-03-02 16:48:24 -080026108 }];
Ed Tanous4758d5b2017-06-06 15:28:13 -070026109
26110 // Helpers
26111 function arrayDifference(tokens1, tokens2) {
26112 if (!tokens1 || !tokens1.length) return [];
26113 if (!tokens2 || !tokens2.length) return tokens1;
26114
26115 var values = [];
26116
26117 outer:
26118 for (var i = 0; i < tokens1.length; i++) {
26119 var token = tokens1[i];
26120 for (var j = 0; j < tokens2.length; j++) {
26121 if (token === tokens2[j]) continue outer;
26122 }
26123 values.push(token);
26124 }
26125
26126 return values;
26127 }
26128
26129 function split(classString) {
26130 return classString && classString.split(' ');
26131 }
26132
26133 function toClassString(classValue) {
26134 var classString = classValue;
26135
26136 if (isArray(classValue)) {
26137 classString = classValue.map(toClassString).join(' ');
26138 } else if (isObject(classValue)) {
26139 classString = Object.keys(classValue).
26140 filter(function(key) { return classValue[key]; }).
26141 join(' ');
26142 }
26143
26144 return classString;
26145 }
Ed Tanous904063f2017-03-02 16:48:24 -080026146}
26147
26148/**
26149 * @ngdoc directive
26150 * @name ngClass
26151 * @restrict AC
26152 *
26153 * @description
26154 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
26155 * an expression that represents all classes to be added.
26156 *
26157 * The directive operates in three different ways, depending on which of three types the expression
26158 * evaluates to:
26159 *
26160 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
26161 * names.
26162 *
26163 * 2. If the expression evaluates to an object, then for each key-value pair of the
26164 * object with a truthy value the corresponding key is used as a class name.
26165 *
26166 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
26167 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
26168 * to give you more control over what CSS classes appear. See the code below for an example of this.
26169 *
26170 *
26171 * The directive won't add duplicate classes if a particular class was already set.
26172 *
26173 * When the expression changes, the previously added classes are removed and only then are the
26174 * new classes added.
26175 *
26176 * @knownIssue
26177 * You should not use {@link guide/interpolation interpolation} in the value of the `class`
26178 * attribute, when using the `ngClass` directive on the same element.
26179 * See {@link guide/interpolation#known-issues here} for more info.
26180 *
26181 * @animations
26182 * | Animation | Occurs |
26183 * |----------------------------------|-------------------------------------|
26184 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
26185 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
26186 *
26187 * @element ANY
26188 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
26189 * of the evaluation can be a string representing space delimited class
26190 * names, an array, or a map of class names to boolean values. In the case of a map, the
26191 * names of the properties whose values are truthy will be added as css classes to the
26192 * element.
26193 *
26194 * @example Example that demonstrates basic bindings via ngClass directive.
Ed Tanous4758d5b2017-06-06 15:28:13 -070026195 <example name="ng-class">
Ed Tanous904063f2017-03-02 16:48:24 -080026196 <file name="index.html">
26197 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
26198 <label>
26199 <input type="checkbox" ng-model="deleted">
26200 deleted (apply "strike" class)
26201 </label><br>
26202 <label>
26203 <input type="checkbox" ng-model="important">
26204 important (apply "bold" class)
26205 </label><br>
26206 <label>
26207 <input type="checkbox" ng-model="error">
26208 error (apply "has-error" class)
26209 </label>
26210 <hr>
26211 <p ng-class="style">Using String Syntax</p>
26212 <input type="text" ng-model="style"
26213 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
26214 <hr>
26215 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
26216 <input ng-model="style1"
26217 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
26218 <input ng-model="style2"
26219 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
26220 <input ng-model="style3"
26221 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
26222 <hr>
26223 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
26224 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
26225 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
26226 </file>
26227 <file name="style.css">
26228 .strike {
26229 text-decoration: line-through;
26230 }
26231 .bold {
26232 font-weight: bold;
26233 }
26234 .red {
26235 color: red;
26236 }
26237 .has-error {
26238 color: red;
26239 background-color: yellow;
26240 }
26241 .orange {
26242 color: orange;
26243 }
26244 </file>
26245 <file name="protractor.js" type="protractor">
26246 var ps = element.all(by.css('p'));
26247
26248 it('should let you toggle the class', function() {
26249
26250 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
26251 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
26252
26253 element(by.model('important')).click();
26254 expect(ps.first().getAttribute('class')).toMatch(/bold/);
26255
26256 element(by.model('error')).click();
26257 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
26258 });
26259
26260 it('should let you toggle string example', function() {
26261 expect(ps.get(1).getAttribute('class')).toBe('');
26262 element(by.model('style')).clear();
26263 element(by.model('style')).sendKeys('red');
26264 expect(ps.get(1).getAttribute('class')).toBe('red');
26265 });
26266
26267 it('array example should have 3 classes', function() {
26268 expect(ps.get(2).getAttribute('class')).toBe('');
26269 element(by.model('style1')).sendKeys('bold');
26270 element(by.model('style2')).sendKeys('strike');
26271 element(by.model('style3')).sendKeys('red');
26272 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
26273 });
26274
26275 it('array with map example should have 2 classes', function() {
26276 expect(ps.last().getAttribute('class')).toBe('');
26277 element(by.model('style4')).sendKeys('bold');
26278 element(by.model('warning')).click();
26279 expect(ps.last().getAttribute('class')).toBe('bold orange');
26280 });
26281 </file>
26282 </example>
26283
26284 ## Animations
26285
26286 The example below demonstrates how to perform animations using ngClass.
26287
Ed Tanous4758d5b2017-06-06 15:28:13 -070026288 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class">
Ed Tanous904063f2017-03-02 16:48:24 -080026289 <file name="index.html">
26290 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
26291 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
26292 <br>
26293 <span class="base-class" ng-class="myVar">Sample Text</span>
26294 </file>
26295 <file name="style.css">
26296 .base-class {
26297 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
26298 }
26299
26300 .base-class.my-class {
26301 color: red;
26302 font-size:3em;
26303 }
26304 </file>
26305 <file name="protractor.js" type="protractor">
26306 it('should check ng-class', function() {
26307 expect(element(by.css('.base-class')).getAttribute('class')).not.
26308 toMatch(/my-class/);
26309
26310 element(by.id('setbtn')).click();
26311
26312 expect(element(by.css('.base-class')).getAttribute('class')).
26313 toMatch(/my-class/);
26314
26315 element(by.id('clearbtn')).click();
26316
26317 expect(element(by.css('.base-class')).getAttribute('class')).not.
26318 toMatch(/my-class/);
26319 });
26320 </file>
26321 </example>
26322
26323
26324 ## ngClass and pre-existing CSS3 Transitions/Animations
26325 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
26326 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
26327 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
26328 to view the step by step details of {@link $animate#addClass $animate.addClass} and
26329 {@link $animate#removeClass $animate.removeClass}.
26330 */
26331var ngClassDirective = classDirective('', true);
26332
26333/**
26334 * @ngdoc directive
26335 * @name ngClassOdd
26336 * @restrict AC
26337 *
26338 * @description
26339 * The `ngClassOdd` and `ngClassEven` directives work exactly as
26340 * {@link ng.directive:ngClass ngClass}, except they work in
26341 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
26342 *
26343 * This directive can be applied only within the scope of an
26344 * {@link ng.directive:ngRepeat ngRepeat}.
26345 *
26346 * @element ANY
26347 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
26348 * of the evaluation can be a string representing space delimited class names or an array.
26349 *
26350 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070026351 <example name="ng-class-odd">
Ed Tanous904063f2017-03-02 16:48:24 -080026352 <file name="index.html">
26353 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
26354 <li ng-repeat="name in names">
26355 <span ng-class-odd="'odd'" ng-class-even="'even'">
26356 {{name}}
26357 </span>
26358 </li>
26359 </ol>
26360 </file>
26361 <file name="style.css">
26362 .odd {
26363 color: red;
26364 }
26365 .even {
26366 color: blue;
26367 }
26368 </file>
26369 <file name="protractor.js" type="protractor">
26370 it('should check ng-class-odd and ng-class-even', function() {
26371 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
26372 toMatch(/odd/);
26373 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
26374 toMatch(/even/);
26375 });
26376 </file>
26377 </example>
26378 */
26379var ngClassOddDirective = classDirective('Odd', 0);
26380
26381/**
26382 * @ngdoc directive
26383 * @name ngClassEven
26384 * @restrict AC
26385 *
26386 * @description
26387 * The `ngClassOdd` and `ngClassEven` directives work exactly as
26388 * {@link ng.directive:ngClass ngClass}, except they work in
26389 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
26390 *
26391 * This directive can be applied only within the scope of an
26392 * {@link ng.directive:ngRepeat ngRepeat}.
26393 *
26394 * @element ANY
26395 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
26396 * result of the evaluation can be a string representing space delimited class names or an array.
26397 *
26398 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070026399 <example name="ng-class-even">
Ed Tanous904063f2017-03-02 16:48:24 -080026400 <file name="index.html">
26401 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
26402 <li ng-repeat="name in names">
26403 <span ng-class-odd="'odd'" ng-class-even="'even'">
26404 {{name}} &nbsp; &nbsp; &nbsp;
26405 </span>
26406 </li>
26407 </ol>
26408 </file>
26409 <file name="style.css">
26410 .odd {
26411 color: red;
26412 }
26413 .even {
26414 color: blue;
26415 }
26416 </file>
26417 <file name="protractor.js" type="protractor">
26418 it('should check ng-class-odd and ng-class-even', function() {
26419 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
26420 toMatch(/odd/);
26421 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
26422 toMatch(/even/);
26423 });
26424 </file>
26425 </example>
26426 */
26427var ngClassEvenDirective = classDirective('Even', 1);
26428
26429/**
26430 * @ngdoc directive
26431 * @name ngCloak
26432 * @restrict AC
26433 *
26434 * @description
26435 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
26436 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
26437 * directive to avoid the undesirable flicker effect caused by the html template display.
26438 *
26439 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
26440 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
26441 * of the browser view.
26442 *
26443 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
26444 * `angular.min.js`.
26445 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
26446 *
26447 * ```css
26448 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
26449 * display: none !important;
26450 * }
26451 * ```
26452 *
26453 * When this css rule is loaded by the browser, all html elements (including their children) that
26454 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
26455 * during the compilation of the template it deletes the `ngCloak` element attribute, making
26456 * the compiled element visible.
26457 *
26458 * For the best result, the `angular.js` script must be loaded in the head section of the html
26459 * document; alternatively, the css rule above must be included in the external stylesheet of the
26460 * application.
26461 *
26462 * @element ANY
26463 *
26464 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070026465 <example name="ng-cloak">
Ed Tanous904063f2017-03-02 16:48:24 -080026466 <file name="index.html">
26467 <div id="template1" ng-cloak>{{ 'hello' }}</div>
26468 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
26469 </file>
26470 <file name="protractor.js" type="protractor">
26471 it('should remove the template directive and css class', function() {
26472 expect($('#template1').getAttribute('ng-cloak')).
26473 toBeNull();
26474 expect($('#template2').getAttribute('ng-cloak')).
26475 toBeNull();
26476 });
26477 </file>
26478 </example>
26479 *
26480 */
26481var ngCloakDirective = ngDirective({
26482 compile: function(element, attr) {
26483 attr.$set('ngCloak', undefined);
26484 element.removeClass('ng-cloak');
26485 }
26486});
26487
26488/**
26489 * @ngdoc directive
26490 * @name ngController
26491 *
26492 * @description
26493 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
26494 * supports the principles behind the Model-View-Controller design pattern.
26495 *
26496 * MVC components in angular:
26497 *
26498 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
26499 * are accessed through bindings.
26500 * * View — The template (HTML with data bindings) that is rendered into the View.
26501 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
26502 * logic behind the application to decorate the scope with functions and values
26503 *
26504 * Note that you can also attach controllers to the DOM by declaring it in a route definition
26505 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
26506 * again using `ng-controller` in the template itself. This will cause the controller to be attached
26507 * and executed twice.
26508 *
26509 * @element ANY
26510 * @scope
26511 * @priority 500
26512 * @param {expression} ngController Name of a constructor function registered with the current
26513 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
26514 * that on the current scope evaluates to a constructor function.
26515 *
26516 * The controller instance can be published into a scope property by specifying
26517 * `ng-controller="as propertyName"`.
26518 *
26519 * If the current `$controllerProvider` is configured to use globals (via
26520 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
Ed Tanous4758d5b2017-06-06 15:28:13 -070026521 * also be the name of a globally accessible constructor function (deprecated, not recommended).
Ed Tanous904063f2017-03-02 16:48:24 -080026522 *
26523 * @example
26524 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
26525 * greeting are methods declared on the controller (see source tab). These methods can
26526 * easily be called from the angular markup. Any changes to the data are automatically reflected
26527 * in the View without the need for a manual update.
26528 *
26529 * Two different declaration styles are included below:
26530 *
26531 * * one binds methods and properties directly onto the controller using `this`:
26532 * `ng-controller="SettingsController1 as settings"`
26533 * * one injects `$scope` into the controller:
26534 * `ng-controller="SettingsController2"`
26535 *
26536 * The second option is more common in the Angular community, and is generally used in boilerplates
26537 * and in this guide. However, there are advantages to binding properties directly to the controller
26538 * and avoiding scope.
26539 *
26540 * * Using `controller as` makes it obvious which controller you are accessing in the template when
26541 * multiple controllers apply to an element.
26542 * * If you are writing your controllers as classes you have easier access to the properties and
26543 * methods, which will appear on the scope, from inside the controller code.
26544 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
26545 * inheritance masking primitives.
26546 *
26547 * This example demonstrates the `controller as` syntax.
26548 *
26549 * <example name="ngControllerAs" module="controllerAsExample">
26550 * <file name="index.html">
26551 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
26552 * <label>Name: <input type="text" ng-model="settings.name"/></label>
26553 * <button ng-click="settings.greet()">greet</button><br/>
26554 * Contact:
26555 * <ul>
26556 * <li ng-repeat="contact in settings.contacts">
26557 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
26558 * <option>phone</option>
26559 * <option>email</option>
26560 * </select>
26561 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
26562 * <button ng-click="settings.clearContact(contact)">clear</button>
26563 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
26564 * </li>
26565 * <li><button ng-click="settings.addContact()">add</button></li>
26566 * </ul>
26567 * </div>
26568 * </file>
26569 * <file name="app.js">
26570 * angular.module('controllerAsExample', [])
26571 * .controller('SettingsController1', SettingsController1);
26572 *
26573 * function SettingsController1() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070026574 * this.name = 'John Smith';
Ed Tanous904063f2017-03-02 16:48:24 -080026575 * this.contacts = [
26576 * {type: 'phone', value: '408 555 1212'},
Ed Tanous4758d5b2017-06-06 15:28:13 -070026577 * {type: 'email', value: 'john.smith@example.org'}
26578 * ];
Ed Tanous904063f2017-03-02 16:48:24 -080026579 * }
26580 *
26581 * SettingsController1.prototype.greet = function() {
26582 * alert(this.name);
26583 * };
26584 *
26585 * SettingsController1.prototype.addContact = function() {
26586 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
26587 * };
26588 *
26589 * SettingsController1.prototype.removeContact = function(contactToRemove) {
26590 * var index = this.contacts.indexOf(contactToRemove);
26591 * this.contacts.splice(index, 1);
26592 * };
26593 *
26594 * SettingsController1.prototype.clearContact = function(contact) {
26595 * contact.type = 'phone';
26596 * contact.value = '';
26597 * };
26598 * </file>
26599 * <file name="protractor.js" type="protractor">
26600 * it('should check controller as', function() {
26601 * var container = element(by.id('ctrl-as-exmpl'));
26602 * expect(container.element(by.model('settings.name'))
26603 * .getAttribute('value')).toBe('John Smith');
26604 *
26605 * var firstRepeat =
26606 * container.element(by.repeater('contact in settings.contacts').row(0));
26607 * var secondRepeat =
26608 * container.element(by.repeater('contact in settings.contacts').row(1));
26609 *
26610 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26611 * .toBe('408 555 1212');
26612 *
26613 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
26614 * .toBe('john.smith@example.org');
26615 *
26616 * firstRepeat.element(by.buttonText('clear')).click();
26617 *
26618 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26619 * .toBe('');
26620 *
26621 * container.element(by.buttonText('add')).click();
26622 *
26623 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
26624 * .element(by.model('contact.value'))
26625 * .getAttribute('value'))
26626 * .toBe('yourname@example.org');
26627 * });
26628 * </file>
26629 * </example>
26630 *
26631 * This example demonstrates the "attach to `$scope`" style of controller.
26632 *
26633 * <example name="ngController" module="controllerExample">
26634 * <file name="index.html">
26635 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
26636 * <label>Name: <input type="text" ng-model="name"/></label>
26637 * <button ng-click="greet()">greet</button><br/>
26638 * Contact:
26639 * <ul>
26640 * <li ng-repeat="contact in contacts">
26641 * <select ng-model="contact.type" id="select_{{$index}}">
26642 * <option>phone</option>
26643 * <option>email</option>
26644 * </select>
26645 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
26646 * <button ng-click="clearContact(contact)">clear</button>
26647 * <button ng-click="removeContact(contact)">X</button>
26648 * </li>
26649 * <li>[ <button ng-click="addContact()">add</button> ]</li>
26650 * </ul>
26651 * </div>
26652 * </file>
26653 * <file name="app.js">
26654 * angular.module('controllerExample', [])
26655 * .controller('SettingsController2', ['$scope', SettingsController2]);
26656 *
26657 * function SettingsController2($scope) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070026658 * $scope.name = 'John Smith';
Ed Tanous904063f2017-03-02 16:48:24 -080026659 * $scope.contacts = [
26660 * {type:'phone', value:'408 555 1212'},
Ed Tanous4758d5b2017-06-06 15:28:13 -070026661 * {type:'email', value:'john.smith@example.org'}
26662 * ];
Ed Tanous904063f2017-03-02 16:48:24 -080026663 *
26664 * $scope.greet = function() {
26665 * alert($scope.name);
26666 * };
26667 *
26668 * $scope.addContact = function() {
26669 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
26670 * };
26671 *
26672 * $scope.removeContact = function(contactToRemove) {
26673 * var index = $scope.contacts.indexOf(contactToRemove);
26674 * $scope.contacts.splice(index, 1);
26675 * };
26676 *
26677 * $scope.clearContact = function(contact) {
26678 * contact.type = 'phone';
26679 * contact.value = '';
26680 * };
26681 * }
26682 * </file>
26683 * <file name="protractor.js" type="protractor">
26684 * it('should check controller', function() {
26685 * var container = element(by.id('ctrl-exmpl'));
26686 *
26687 * expect(container.element(by.model('name'))
26688 * .getAttribute('value')).toBe('John Smith');
26689 *
26690 * var firstRepeat =
26691 * container.element(by.repeater('contact in contacts').row(0));
26692 * var secondRepeat =
26693 * container.element(by.repeater('contact in contacts').row(1));
26694 *
26695 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26696 * .toBe('408 555 1212');
26697 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
26698 * .toBe('john.smith@example.org');
26699 *
26700 * firstRepeat.element(by.buttonText('clear')).click();
26701 *
26702 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26703 * .toBe('');
26704 *
26705 * container.element(by.buttonText('add')).click();
26706 *
26707 * expect(container.element(by.repeater('contact in contacts').row(2))
26708 * .element(by.model('contact.value'))
26709 * .getAttribute('value'))
26710 * .toBe('yourname@example.org');
26711 * });
26712 * </file>
26713 *</example>
26714
26715 */
26716var ngControllerDirective = [function() {
26717 return {
26718 restrict: 'A',
26719 scope: true,
26720 controller: '@',
26721 priority: 500
26722 };
26723}];
26724
26725/**
26726 * @ngdoc directive
26727 * @name ngCsp
26728 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026729 * @restrict A
26730 * @element ANY
Ed Tanous904063f2017-03-02 16:48:24 -080026731 * @description
26732 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026733 * Angular has some features that can conflict with certain restrictions that are applied when using
Ed Tanous904063f2017-03-02 16:48:24 -080026734 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
26735 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026736 * If you intend to implement CSP with these rules then you must tell Angular not to use these
26737 * features.
Ed Tanous904063f2017-03-02 16:48:24 -080026738 *
26739 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
26740 *
26741 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026742 * The following default rules in CSP affect Angular:
Ed Tanous904063f2017-03-02 16:48:24 -080026743 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026744 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute
26745 * code from strings is forbidden. Angular makes use of this in the {@link $parse} service to
26746 * provide a 30% increase in the speed of evaluating Angular expressions. (This CSP rule can be
26747 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would
26748 * weaken the protections offered by CSP.)
Ed Tanous904063f2017-03-02 16:48:24 -080026749 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026750 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden.
26751 * This prevents apps from injecting custom styles directly into the document. Angular makes use of
26752 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these
26753 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css`
26754 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but
26755 * it is generally not recommended as it would weaken the protections offered by CSP.)
Ed Tanous904063f2017-03-02 16:48:24 -080026756 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026757 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking dynamic code
26758 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically
26759 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a
26760 * CSP error to be logged in the console:
Ed Tanous904063f2017-03-02 16:48:24 -080026761 *
26762 * ```
26763 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
26764 * script in the following Content Security Policy directive: "default-src 'self'". Note that
26765 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
26766 * ```
26767 *
26768 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
26769 * directive on an element of the HTML document that appears before the `<script>` tag that loads
26770 * the `angular.js` file.
26771 *
26772 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
26773 *
26774 * You can specify which of the CSP related Angular features should be deactivated by providing
26775 * a value for the `ng-csp` attribute. The options are as follows:
26776 *
26777 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
26778 *
26779 * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings
26780 *
26781 * You can use these values in the following combinations:
26782 *
26783 *
26784 * * No declaration means that Angular will assume that you can do inline styles, but it will do
Ed Tanous4758d5b2017-06-06 15:28:13 -070026785 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous
26786 * versions of Angular.
Ed Tanous904063f2017-03-02 16:48:24 -080026787 *
26788 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
Ed Tanous4758d5b2017-06-06 15:28:13 -070026789 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous
26790 * versions of Angular.
Ed Tanous904063f2017-03-02 16:48:24 -080026791 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070026792 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can
26793 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
Ed Tanous904063f2017-03-02 16:48:24 -080026794 *
26795 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
26796 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
26797 *
26798 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
26799 * styles nor use eval, which is the same as an empty: ng-csp.
26800 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
26801 *
26802 * @example
26803 * This example shows how to apply the `ngCsp` directive to the `html` tag.
26804 ```html
26805 <!doctype html>
26806 <html ng-app ng-csp>
26807 ...
26808 ...
26809 </html>
26810 ```
26811 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070026812 <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! -->
Ed Tanous904063f2017-03-02 16:48:24 -080026813 <example name="example.csp" module="cspExample" ng-csp="true">
26814 <file name="index.html">
26815 <div ng-controller="MainController as ctrl">
26816 <div>
26817 <button ng-click="ctrl.inc()" id="inc">Increment</button>
26818 <span id="counter">
26819 {{ctrl.counter}}
26820 </span>
26821 </div>
26822
26823 <div>
26824 <button ng-click="ctrl.evil()" id="evil">Evil</button>
26825 <span id="evilError">
26826 {{ctrl.evilError}}
26827 </span>
26828 </div>
26829 </div>
26830 </file>
26831 <file name="script.js">
26832 angular.module('cspExample', [])
Ed Tanous4758d5b2017-06-06 15:28:13 -070026833 .controller('MainController', function MainController() {
Ed Tanous904063f2017-03-02 16:48:24 -080026834 this.counter = 0;
26835 this.inc = function() {
26836 this.counter++;
26837 };
26838 this.evil = function() {
Ed Tanous904063f2017-03-02 16:48:24 -080026839 try {
Ed Tanous4758d5b2017-06-06 15:28:13 -070026840 eval('1+2'); // eslint-disable-line no-eval
Ed Tanous904063f2017-03-02 16:48:24 -080026841 } catch (e) {
26842 this.evilError = e.message;
26843 }
26844 };
26845 });
26846 </file>
26847 <file name="protractor.js" type="protractor">
26848 var util, webdriver;
26849
26850 var incBtn = element(by.id('inc'));
26851 var counter = element(by.id('counter'));
26852 var evilBtn = element(by.id('evil'));
26853 var evilError = element(by.id('evilError'));
26854
26855 function getAndClearSevereErrors() {
26856 return browser.manage().logs().get('browser').then(function(browserLog) {
26857 return browserLog.filter(function(logEntry) {
26858 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
26859 });
26860 });
26861 }
26862
26863 function clearErrors() {
26864 getAndClearSevereErrors();
26865 }
26866
26867 function expectNoErrors() {
26868 getAndClearSevereErrors().then(function(filteredLog) {
26869 expect(filteredLog.length).toEqual(0);
26870 if (filteredLog.length) {
26871 console.log('browser console errors: ' + util.inspect(filteredLog));
26872 }
26873 });
26874 }
26875
26876 function expectError(regex) {
26877 getAndClearSevereErrors().then(function(filteredLog) {
26878 var found = false;
26879 filteredLog.forEach(function(log) {
26880 if (log.message.match(regex)) {
26881 found = true;
26882 }
26883 });
26884 if (!found) {
26885 throw new Error('expected an error that matches ' + regex);
26886 }
26887 });
26888 }
26889
26890 beforeEach(function() {
26891 util = require('util');
Ed Tanous4758d5b2017-06-06 15:28:13 -070026892 webdriver = require('selenium-webdriver');
Ed Tanous904063f2017-03-02 16:48:24 -080026893 });
26894
26895 // For now, we only test on Chrome,
26896 // as Safari does not load the page with Protractor's injected scripts,
26897 // and Firefox webdriver always disables content security policy (#6358)
26898 if (browser.params.browser !== 'chrome') {
26899 return;
26900 }
26901
26902 it('should not report errors when the page is loaded', function() {
26903 // clear errors so we are not dependent on previous tests
26904 clearErrors();
26905 // Need to reload the page as the page is already loaded when
26906 // we come here
26907 browser.driver.getCurrentUrl().then(function(url) {
26908 browser.get(url);
26909 });
26910 expectNoErrors();
26911 });
26912
26913 it('should evaluate expressions', function() {
26914 expect(counter.getText()).toEqual('0');
26915 incBtn.click();
26916 expect(counter.getText()).toEqual('1');
26917 expectNoErrors();
26918 });
26919
26920 it('should throw and report an error when using "eval"', function() {
26921 evilBtn.click();
26922 expect(evilError.getText()).toMatch(/Content Security Policy/);
26923 expectError(/Content Security Policy/);
26924 });
26925 </file>
26926 </example>
26927 */
26928
Ed Tanous4758d5b2017-06-06 15:28:13 -070026929// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while
26930// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()`
26931// fn that looks for the `ng-csp` attribute anywhere in the current doc.
Ed Tanous904063f2017-03-02 16:48:24 -080026932
26933/**
26934 * @ngdoc directive
26935 * @name ngClick
26936 *
26937 * @description
26938 * The ngClick directive allows you to specify custom behavior when
26939 * an element is clicked.
26940 *
26941 * @element ANY
26942 * @priority 0
26943 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
26944 * click. ({@link guide/expression#-event- Event object is available as `$event`})
26945 *
26946 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070026947 <example name="ng-click">
Ed Tanous904063f2017-03-02 16:48:24 -080026948 <file name="index.html">
26949 <button ng-click="count = count + 1" ng-init="count=0">
26950 Increment
26951 </button>
26952 <span>
26953 count: {{count}}
26954 </span>
26955 </file>
26956 <file name="protractor.js" type="protractor">
26957 it('should check ng-click', function() {
26958 expect(element(by.binding('count')).getText()).toMatch('0');
26959 element(by.css('button')).click();
26960 expect(element(by.binding('count')).getText()).toMatch('1');
26961 });
26962 </file>
26963 </example>
26964 */
26965/*
26966 * A collection of directives that allows creation of custom event handlers that are defined as
26967 * angular expressions and are compiled and executed within the current scope.
26968 */
26969var ngEventDirectives = {};
26970
26971// For events that might fire synchronously during DOM manipulation
26972// we need to execute their event handlers asynchronously using $evalAsync,
26973// so that they are not executed in an inconsistent state.
26974var forceAsyncEvents = {
26975 'blur': true,
26976 'focus': true
26977};
26978forEach(
26979 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
26980 function(eventName) {
26981 var directiveName = directiveNormalize('ng-' + eventName);
26982 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
26983 return {
26984 restrict: 'A',
26985 compile: function($element, attr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070026986 // NOTE:
26987 // We expose the powerful `$event` object on the scope that provides access to the Window,
26988 // etc. This is OK, because expressions are not sandboxed any more (and the expression
26989 // sandbox was never meant to be a security feature anyway).
26990 var fn = $parse(attr[directiveName]);
Ed Tanous904063f2017-03-02 16:48:24 -080026991 return function ngEventHandler(scope, element) {
26992 element.on(eventName, function(event) {
26993 var callback = function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070026994 fn(scope, {$event: event});
Ed Tanous904063f2017-03-02 16:48:24 -080026995 };
26996 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
26997 scope.$evalAsync(callback);
26998 } else {
26999 scope.$apply(callback);
27000 }
27001 });
27002 };
27003 }
27004 };
27005 }];
27006 }
27007);
27008
27009/**
27010 * @ngdoc directive
27011 * @name ngDblclick
27012 *
27013 * @description
27014 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
27015 *
27016 * @element ANY
27017 * @priority 0
27018 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
27019 * a dblclick. (The Event object is available as `$event`)
27020 *
27021 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027022 <example name="ng-dblclick">
Ed Tanous904063f2017-03-02 16:48:24 -080027023 <file name="index.html">
27024 <button ng-dblclick="count = count + 1" ng-init="count=0">
27025 Increment (on double click)
27026 </button>
27027 count: {{count}}
27028 </file>
27029 </example>
27030 */
27031
27032
27033/**
27034 * @ngdoc directive
27035 * @name ngMousedown
27036 *
27037 * @description
27038 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
27039 *
27040 * @element ANY
27041 * @priority 0
27042 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
27043 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
27044 *
27045 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027046 <example name="ng-mousedown">
Ed Tanous904063f2017-03-02 16:48:24 -080027047 <file name="index.html">
27048 <button ng-mousedown="count = count + 1" ng-init="count=0">
27049 Increment (on mouse down)
27050 </button>
27051 count: {{count}}
27052 </file>
27053 </example>
27054 */
27055
27056
27057/**
27058 * @ngdoc directive
27059 * @name ngMouseup
27060 *
27061 * @description
27062 * Specify custom behavior on mouseup event.
27063 *
27064 * @element ANY
27065 * @priority 0
27066 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
27067 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
27068 *
27069 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027070 <example name="ng-mouseup">
Ed Tanous904063f2017-03-02 16:48:24 -080027071 <file name="index.html">
27072 <button ng-mouseup="count = count + 1" ng-init="count=0">
27073 Increment (on mouse up)
27074 </button>
27075 count: {{count}}
27076 </file>
27077 </example>
27078 */
27079
27080/**
27081 * @ngdoc directive
27082 * @name ngMouseover
27083 *
27084 * @description
27085 * Specify custom behavior on mouseover event.
27086 *
27087 * @element ANY
27088 * @priority 0
27089 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
27090 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
27091 *
27092 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027093 <example name="ng-mouseover">
Ed Tanous904063f2017-03-02 16:48:24 -080027094 <file name="index.html">
27095 <button ng-mouseover="count = count + 1" ng-init="count=0">
27096 Increment (when mouse is over)
27097 </button>
27098 count: {{count}}
27099 </file>
27100 </example>
27101 */
27102
27103
27104/**
27105 * @ngdoc directive
27106 * @name ngMouseenter
27107 *
27108 * @description
27109 * Specify custom behavior on mouseenter event.
27110 *
27111 * @element ANY
27112 * @priority 0
27113 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
27114 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
27115 *
27116 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027117 <example name="ng-mouseenter">
Ed Tanous904063f2017-03-02 16:48:24 -080027118 <file name="index.html">
27119 <button ng-mouseenter="count = count + 1" ng-init="count=0">
27120 Increment (when mouse enters)
27121 </button>
27122 count: {{count}}
27123 </file>
27124 </example>
27125 */
27126
27127
27128/**
27129 * @ngdoc directive
27130 * @name ngMouseleave
27131 *
27132 * @description
27133 * Specify custom behavior on mouseleave event.
27134 *
27135 * @element ANY
27136 * @priority 0
27137 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
27138 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
27139 *
27140 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027141 <example name="ng-mouseleave">
Ed Tanous904063f2017-03-02 16:48:24 -080027142 <file name="index.html">
27143 <button ng-mouseleave="count = count + 1" ng-init="count=0">
27144 Increment (when mouse leaves)
27145 </button>
27146 count: {{count}}
27147 </file>
27148 </example>
27149 */
27150
27151
27152/**
27153 * @ngdoc directive
27154 * @name ngMousemove
27155 *
27156 * @description
27157 * Specify custom behavior on mousemove event.
27158 *
27159 * @element ANY
27160 * @priority 0
27161 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
27162 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
27163 *
27164 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027165 <example name="ng-mousemove">
Ed Tanous904063f2017-03-02 16:48:24 -080027166 <file name="index.html">
27167 <button ng-mousemove="count = count + 1" ng-init="count=0">
27168 Increment (when mouse moves)
27169 </button>
27170 count: {{count}}
27171 </file>
27172 </example>
27173 */
27174
27175
27176/**
27177 * @ngdoc directive
27178 * @name ngKeydown
27179 *
27180 * @description
27181 * Specify custom behavior on keydown event.
27182 *
27183 * @element ANY
27184 * @priority 0
27185 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
27186 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
27187 *
27188 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027189 <example name="ng-keydown">
Ed Tanous904063f2017-03-02 16:48:24 -080027190 <file name="index.html">
27191 <input ng-keydown="count = count + 1" ng-init="count=0">
27192 key down count: {{count}}
27193 </file>
27194 </example>
27195 */
27196
27197
27198/**
27199 * @ngdoc directive
27200 * @name ngKeyup
27201 *
27202 * @description
27203 * Specify custom behavior on keyup event.
27204 *
27205 * @element ANY
27206 * @priority 0
27207 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
27208 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
27209 *
27210 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027211 <example name="ng-keyup">
Ed Tanous904063f2017-03-02 16:48:24 -080027212 <file name="index.html">
27213 <p>Typing in the input box below updates the key count</p>
27214 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
27215
27216 <p>Typing in the input box below updates the keycode</p>
27217 <input ng-keyup="event=$event">
27218 <p>event keyCode: {{ event.keyCode }}</p>
27219 <p>event altKey: {{ event.altKey }}</p>
27220 </file>
27221 </example>
27222 */
27223
27224
27225/**
27226 * @ngdoc directive
27227 * @name ngKeypress
27228 *
27229 * @description
27230 * Specify custom behavior on keypress event.
27231 *
27232 * @element ANY
27233 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
27234 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
27235 * and can be interrogated for keyCode, altKey, etc.)
27236 *
27237 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027238 <example name="ng-keypress">
Ed Tanous904063f2017-03-02 16:48:24 -080027239 <file name="index.html">
27240 <input ng-keypress="count = count + 1" ng-init="count=0">
27241 key press count: {{count}}
27242 </file>
27243 </example>
27244 */
27245
27246
27247/**
27248 * @ngdoc directive
27249 * @name ngSubmit
27250 *
27251 * @description
27252 * Enables binding angular expressions to onsubmit events.
27253 *
27254 * Additionally it prevents the default action (which for form means sending the request to the
27255 * server and reloading the current page), but only if the form does not contain `action`,
27256 * `data-action`, or `x-action` attributes.
27257 *
27258 * <div class="alert alert-warning">
27259 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
27260 * `ngSubmit` handlers together. See the
27261 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
27262 * for a detailed discussion of when `ngSubmit` may be triggered.
27263 * </div>
27264 *
27265 * @element form
27266 * @priority 0
27267 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
27268 * ({@link guide/expression#-event- Event object is available as `$event`})
27269 *
27270 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027271 <example module="submitExample" name="ng-submit">
Ed Tanous904063f2017-03-02 16:48:24 -080027272 <file name="index.html">
27273 <script>
27274 angular.module('submitExample', [])
27275 .controller('ExampleController', ['$scope', function($scope) {
27276 $scope.list = [];
27277 $scope.text = 'hello';
27278 $scope.submit = function() {
27279 if ($scope.text) {
27280 $scope.list.push(this.text);
27281 $scope.text = '';
27282 }
27283 };
27284 }]);
27285 </script>
27286 <form ng-submit="submit()" ng-controller="ExampleController">
27287 Enter text and hit enter:
27288 <input type="text" ng-model="text" name="text" />
27289 <input type="submit" id="submit" value="Submit" />
27290 <pre>list={{list}}</pre>
27291 </form>
27292 </file>
27293 <file name="protractor.js" type="protractor">
27294 it('should check ng-submit', function() {
27295 expect(element(by.binding('list')).getText()).toBe('list=[]');
27296 element(by.css('#submit')).click();
27297 expect(element(by.binding('list')).getText()).toContain('hello');
27298 expect(element(by.model('text')).getAttribute('value')).toBe('');
27299 });
27300 it('should ignore empty strings', function() {
27301 expect(element(by.binding('list')).getText()).toBe('list=[]');
27302 element(by.css('#submit')).click();
27303 element(by.css('#submit')).click();
27304 expect(element(by.binding('list')).getText()).toContain('hello');
27305 });
27306 </file>
27307 </example>
27308 */
27309
27310/**
27311 * @ngdoc directive
27312 * @name ngFocus
27313 *
27314 * @description
27315 * Specify custom behavior on focus event.
27316 *
27317 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
27318 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
27319 * during an `$apply` to ensure a consistent state.
27320 *
27321 * @element window, input, select, textarea, a
27322 * @priority 0
27323 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
27324 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
27325 *
27326 * @example
27327 * See {@link ng.directive:ngClick ngClick}
27328 */
27329
27330/**
27331 * @ngdoc directive
27332 * @name ngBlur
27333 *
27334 * @description
27335 * Specify custom behavior on blur event.
27336 *
27337 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
27338 * an element has lost focus.
27339 *
27340 * Note: As the `blur` event is executed synchronously also during DOM manipulations
27341 * (e.g. removing a focussed input),
27342 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
27343 * during an `$apply` to ensure a consistent state.
27344 *
27345 * @element window, input, select, textarea, a
27346 * @priority 0
27347 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
27348 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
27349 *
27350 * @example
27351 * See {@link ng.directive:ngClick ngClick}
27352 */
27353
27354/**
27355 * @ngdoc directive
27356 * @name ngCopy
27357 *
27358 * @description
27359 * Specify custom behavior on copy event.
27360 *
27361 * @element window, input, select, textarea, a
27362 * @priority 0
27363 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
27364 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
27365 *
27366 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027367 <example name="ng-copy">
Ed Tanous904063f2017-03-02 16:48:24 -080027368 <file name="index.html">
27369 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
27370 copied: {{copied}}
27371 </file>
27372 </example>
27373 */
27374
27375/**
27376 * @ngdoc directive
27377 * @name ngCut
27378 *
27379 * @description
27380 * Specify custom behavior on cut event.
27381 *
27382 * @element window, input, select, textarea, a
27383 * @priority 0
27384 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
27385 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
27386 *
27387 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027388 <example name="ng-cut">
Ed Tanous904063f2017-03-02 16:48:24 -080027389 <file name="index.html">
27390 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
27391 cut: {{cut}}
27392 </file>
27393 </example>
27394 */
27395
27396/**
27397 * @ngdoc directive
27398 * @name ngPaste
27399 *
27400 * @description
27401 * Specify custom behavior on paste event.
27402 *
27403 * @element window, input, select, textarea, a
27404 * @priority 0
27405 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
27406 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
27407 *
27408 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027409 <example name="ng-paste">
Ed Tanous904063f2017-03-02 16:48:24 -080027410 <file name="index.html">
27411 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
27412 pasted: {{paste}}
27413 </file>
27414 </example>
27415 */
27416
27417/**
27418 * @ngdoc directive
27419 * @name ngIf
27420 * @restrict A
27421 * @multiElement
27422 *
27423 * @description
27424 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
27425 * {expression}. If the expression assigned to `ngIf` evaluates to a false
27426 * value then the element is removed from the DOM, otherwise a clone of the
27427 * element is reinserted into the DOM.
27428 *
27429 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
27430 * element in the DOM rather than changing its visibility via the `display` css property. A common
27431 * case when this difference is significant is when using css selectors that rely on an element's
27432 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
27433 *
27434 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
27435 * is created when the element is restored. The scope created within `ngIf` inherits from
27436 * its parent scope using
27437 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
27438 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
27439 * a javascript primitive defined in the parent scope. In this case any modifications made to the
27440 * variable within the child scope will override (hide) the value in the parent scope.
27441 *
27442 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
27443 * is if an element's class attribute is directly modified after it's compiled, using something like
27444 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
27445 * the added class will be lost because the original compiled state is used to regenerate the element.
27446 *
27447 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
27448 * and `leave` effects.
27449 *
27450 * @animations
27451 * | Animation | Occurs |
27452 * |----------------------------------|-------------------------------------|
27453 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container |
27454 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM |
27455 *
27456 * @element ANY
27457 * @scope
27458 * @priority 600
27459 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
27460 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
27461 * element is added to the DOM tree.
27462 *
27463 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027464 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-if">
Ed Tanous904063f2017-03-02 16:48:24 -080027465 <file name="index.html">
27466 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
27467 Show when checked:
27468 <span ng-if="checked" class="animate-if">
27469 This is removed when the checkbox is unchecked.
27470 </span>
27471 </file>
27472 <file name="animations.css">
27473 .animate-if {
27474 background:white;
27475 border:1px solid black;
27476 padding:10px;
27477 }
27478
27479 .animate-if.ng-enter, .animate-if.ng-leave {
27480 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
27481 }
27482
27483 .animate-if.ng-enter,
27484 .animate-if.ng-leave.ng-leave-active {
27485 opacity:0;
27486 }
27487
27488 .animate-if.ng-leave,
27489 .animate-if.ng-enter.ng-enter-active {
27490 opacity:1;
27491 }
27492 </file>
27493 </example>
27494 */
27495var ngIfDirective = ['$animate', '$compile', function($animate, $compile) {
27496 return {
27497 multiElement: true,
27498 transclude: 'element',
27499 priority: 600,
27500 terminal: true,
27501 restrict: 'A',
27502 $$tlb: true,
27503 link: function($scope, $element, $attr, ctrl, $transclude) {
27504 var block, childScope, previousElements;
27505 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
27506
27507 if (value) {
27508 if (!childScope) {
27509 $transclude(function(clone, newScope) {
27510 childScope = newScope;
27511 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf);
27512 // Note: We only need the first/last node of the cloned nodes.
27513 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27514 // by a directive with templateUrl when its template arrives.
27515 block = {
27516 clone: clone
27517 };
27518 $animate.enter(clone, $element.parent(), $element);
27519 });
27520 }
27521 } else {
27522 if (previousElements) {
27523 previousElements.remove();
27524 previousElements = null;
27525 }
27526 if (childScope) {
27527 childScope.$destroy();
27528 childScope = null;
27529 }
27530 if (block) {
27531 previousElements = getBlockNodes(block.clone);
Ed Tanous4758d5b2017-06-06 15:28:13 -070027532 $animate.leave(previousElements).done(function(response) {
27533 if (response !== false) previousElements = null;
Ed Tanous904063f2017-03-02 16:48:24 -080027534 });
27535 block = null;
27536 }
27537 }
27538 });
27539 }
27540 };
27541}];
27542
27543/**
27544 * @ngdoc directive
27545 * @name ngInclude
27546 * @restrict ECA
27547 *
27548 * @description
27549 * Fetches, compiles and includes an external HTML fragment.
27550 *
27551 * By default, the template URL is restricted to the same domain and protocol as the
27552 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
27553 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
27554 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
27555 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
27556 * ng.$sce Strict Contextual Escaping}.
27557 *
27558 * In addition, the browser's
27559 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
27560 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
27561 * policy may further restrict whether the template is successfully loaded.
27562 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
27563 * access on some browsers.
27564 *
27565 * @animations
27566 * | Animation | Occurs |
27567 * |----------------------------------|-------------------------------------|
27568 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include |
27569 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include |
27570 *
27571 * The enter and leave animation occur concurrently.
27572 *
27573 * @scope
27574 * @priority 400
27575 *
27576 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
27577 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
27578 * @param {string=} onload Expression to evaluate when a new partial is loaded.
27579 * <div class="alert alert-warning">
27580 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
27581 * a function with the name on the window element, which will usually throw a
27582 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
27583 * different form that {@link guide/directive#normalization matches} `onload`.
27584 * </div>
27585 *
27586 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
27587 * $anchorScroll} to scroll the viewport after the content is loaded.
27588 *
27589 * - If the attribute is not set, disable scrolling.
27590 * - If the attribute is set without value, enable scrolling.
27591 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
27592 *
27593 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027594 <example module="includeExample" deps="angular-animate.js" animations="true" name="ng-include">
Ed Tanous904063f2017-03-02 16:48:24 -080027595 <file name="index.html">
27596 <div ng-controller="ExampleController">
27597 <select ng-model="template" ng-options="t.name for t in templates">
27598 <option value="">(blank)</option>
27599 </select>
27600 url of the template: <code>{{template.url}}</code>
27601 <hr/>
27602 <div class="slide-animate-container">
27603 <div class="slide-animate" ng-include="template.url"></div>
27604 </div>
27605 </div>
27606 </file>
27607 <file name="script.js">
27608 angular.module('includeExample', ['ngAnimate'])
27609 .controller('ExampleController', ['$scope', function($scope) {
27610 $scope.templates =
Ed Tanous4758d5b2017-06-06 15:28:13 -070027611 [{ name: 'template1.html', url: 'template1.html'},
27612 { name: 'template2.html', url: 'template2.html'}];
Ed Tanous904063f2017-03-02 16:48:24 -080027613 $scope.template = $scope.templates[0];
27614 }]);
27615 </file>
27616 <file name="template1.html">
27617 Content of template1.html
27618 </file>
27619 <file name="template2.html">
27620 Content of template2.html
27621 </file>
27622 <file name="animations.css">
27623 .slide-animate-container {
27624 position:relative;
27625 background:white;
27626 border:1px solid black;
27627 height:40px;
27628 overflow:hidden;
27629 }
27630
27631 .slide-animate {
27632 padding:10px;
27633 }
27634
27635 .slide-animate.ng-enter, .slide-animate.ng-leave {
27636 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
27637
27638 position:absolute;
27639 top:0;
27640 left:0;
27641 right:0;
27642 bottom:0;
27643 display:block;
27644 padding:10px;
27645 }
27646
27647 .slide-animate.ng-enter {
27648 top:-50px;
27649 }
27650 .slide-animate.ng-enter.ng-enter-active {
27651 top:0;
27652 }
27653
27654 .slide-animate.ng-leave {
27655 top:0;
27656 }
27657 .slide-animate.ng-leave.ng-leave-active {
27658 top:50px;
27659 }
27660 </file>
27661 <file name="protractor.js" type="protractor">
27662 var templateSelect = element(by.model('template'));
27663 var includeElem = element(by.css('[ng-include]'));
27664
27665 it('should load template1.html', function() {
27666 expect(includeElem.getText()).toMatch(/Content of template1.html/);
27667 });
27668
27669 it('should load template2.html', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070027670 if (browser.params.browser === 'firefox') {
Ed Tanous904063f2017-03-02 16:48:24 -080027671 // Firefox can't handle using selects
27672 // See https://github.com/angular/protractor/issues/480
27673 return;
27674 }
27675 templateSelect.click();
27676 templateSelect.all(by.css('option')).get(2).click();
27677 expect(includeElem.getText()).toMatch(/Content of template2.html/);
27678 });
27679
27680 it('should change to blank', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070027681 if (browser.params.browser === 'firefox') {
Ed Tanous904063f2017-03-02 16:48:24 -080027682 // Firefox can't handle using selects
27683 return;
27684 }
27685 templateSelect.click();
27686 templateSelect.all(by.css('option')).get(0).click();
27687 expect(includeElem.isPresent()).toBe(false);
27688 });
27689 </file>
27690 </example>
27691 */
27692
27693
27694/**
27695 * @ngdoc event
27696 * @name ngInclude#$includeContentRequested
27697 * @eventType emit on the scope ngInclude was declared in
27698 * @description
27699 * Emitted every time the ngInclude content is requested.
27700 *
27701 * @param {Object} angularEvent Synthetic event object.
27702 * @param {String} src URL of content to load.
27703 */
27704
27705
27706/**
27707 * @ngdoc event
27708 * @name ngInclude#$includeContentLoaded
27709 * @eventType emit on the current ngInclude scope
27710 * @description
27711 * Emitted every time the ngInclude content is reloaded.
27712 *
27713 * @param {Object} angularEvent Synthetic event object.
27714 * @param {String} src URL of content to load.
27715 */
27716
27717
27718/**
27719 * @ngdoc event
27720 * @name ngInclude#$includeContentError
27721 * @eventType emit on the scope ngInclude was declared in
27722 * @description
27723 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
27724 *
27725 * @param {Object} angularEvent Synthetic event object.
27726 * @param {String} src URL of content to load.
27727 */
27728var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
27729 function($templateRequest, $anchorScroll, $animate) {
27730 return {
27731 restrict: 'ECA',
27732 priority: 400,
27733 terminal: true,
27734 transclude: 'element',
27735 controller: angular.noop,
27736 compile: function(element, attr) {
27737 var srcExp = attr.ngInclude || attr.src,
27738 onloadExp = attr.onload || '',
27739 autoScrollExp = attr.autoscroll;
27740
27741 return function(scope, $element, $attr, ctrl, $transclude) {
27742 var changeCounter = 0,
27743 currentScope,
27744 previousElement,
27745 currentElement;
27746
27747 var cleanupLastIncludeContent = function() {
27748 if (previousElement) {
27749 previousElement.remove();
27750 previousElement = null;
27751 }
27752 if (currentScope) {
27753 currentScope.$destroy();
27754 currentScope = null;
27755 }
27756 if (currentElement) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070027757 $animate.leave(currentElement).done(function(response) {
27758 if (response !== false) previousElement = null;
Ed Tanous904063f2017-03-02 16:48:24 -080027759 });
27760 previousElement = currentElement;
27761 currentElement = null;
27762 }
27763 };
27764
27765 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070027766 var afterAnimation = function(response) {
27767 if (response !== false && isDefined(autoScrollExp) &&
27768 (!autoScrollExp || scope.$eval(autoScrollExp))) {
27769 $anchorScroll();
Ed Tanous904063f2017-03-02 16:48:24 -080027770 }
27771 };
27772 var thisChangeId = ++changeCounter;
27773
27774 if (src) {
27775 //set the 2nd param to true to ignore the template request error so that the inner
27776 //contents and scope can be cleaned up.
27777 $templateRequest(src, true).then(function(response) {
27778 if (scope.$$destroyed) return;
27779
27780 if (thisChangeId !== changeCounter) return;
27781 var newScope = scope.$new();
27782 ctrl.template = response;
27783
27784 // Note: This will also link all children of ng-include that were contained in the original
27785 // html. If that content contains controllers, ... they could pollute/change the scope.
27786 // However, using ng-include on an element with additional content does not make sense...
27787 // Note: We can't remove them in the cloneAttchFn of $transclude as that
27788 // function is called before linking the content, which would apply child
27789 // directives to non existing elements.
27790 var clone = $transclude(newScope, function(clone) {
27791 cleanupLastIncludeContent();
Ed Tanous4758d5b2017-06-06 15:28:13 -070027792 $animate.enter(clone, null, $element).done(afterAnimation);
Ed Tanous904063f2017-03-02 16:48:24 -080027793 });
27794
27795 currentScope = newScope;
27796 currentElement = clone;
27797
27798 currentScope.$emit('$includeContentLoaded', src);
27799 scope.$eval(onloadExp);
27800 }, function() {
27801 if (scope.$$destroyed) return;
27802
27803 if (thisChangeId === changeCounter) {
27804 cleanupLastIncludeContent();
27805 scope.$emit('$includeContentError', src);
27806 }
27807 });
27808 scope.$emit('$includeContentRequested', src);
27809 } else {
27810 cleanupLastIncludeContent();
27811 ctrl.template = null;
27812 }
27813 });
27814 };
27815 }
27816 };
27817}];
27818
27819// This directive is called during the $transclude call of the first `ngInclude` directive.
27820// It will replace and compile the content of the element with the loaded template.
27821// We need this directive so that the element content is already filled when
27822// the link function of another directive on the same element as ngInclude
27823// is called.
27824var ngIncludeFillContentDirective = ['$compile',
27825 function($compile) {
27826 return {
27827 restrict: 'ECA',
27828 priority: -400,
27829 require: 'ngInclude',
27830 link: function(scope, $element, $attr, ctrl) {
27831 if (toString.call($element[0]).match(/SVG/)) {
27832 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
27833 // support innerHTML, so detect this here and try to generate the contents
27834 // specially.
27835 $element.empty();
27836 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope,
27837 function namespaceAdaptedClone(clone) {
27838 $element.append(clone);
27839 }, {futureParentElement: $element});
27840 return;
27841 }
27842
27843 $element.html(ctrl.template);
27844 $compile($element.contents())(scope);
27845 }
27846 };
27847 }];
27848
27849/**
27850 * @ngdoc directive
27851 * @name ngInit
27852 * @restrict AC
27853 *
27854 * @description
27855 * The `ngInit` directive allows you to evaluate an expression in the
27856 * current scope.
27857 *
27858 * <div class="alert alert-danger">
27859 * This directive can be abused to add unnecessary amounts of logic into your templates.
27860 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
27861 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
27862 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
27863 * rather than `ngInit` to initialize values on a scope.
27864 * </div>
27865 *
27866 * <div class="alert alert-warning">
27867 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
27868 * sure you have parentheses to ensure correct operator precedence:
27869 * <pre class="prettyprint">
27870 * `<div ng-init="test1 = ($index | toString)"></div>`
27871 * </pre>
27872 * </div>
27873 *
27874 * @priority 450
27875 *
27876 * @element ANY
27877 * @param {expression} ngInit {@link guide/expression Expression} to eval.
27878 *
27879 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070027880 <example module="initExample" name="ng-init">
Ed Tanous904063f2017-03-02 16:48:24 -080027881 <file name="index.html">
27882 <script>
27883 angular.module('initExample', [])
27884 .controller('ExampleController', ['$scope', function($scope) {
27885 $scope.list = [['a', 'b'], ['c', 'd']];
27886 }]);
27887 </script>
27888 <div ng-controller="ExampleController">
27889 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
27890 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
27891 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
27892 </div>
27893 </div>
27894 </div>
27895 </file>
27896 <file name="protractor.js" type="protractor">
27897 it('should alias index positions', function() {
27898 var elements = element.all(by.css('.example-init'));
27899 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
27900 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
27901 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
27902 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
27903 });
27904 </file>
27905 </example>
27906 */
27907var ngInitDirective = ngDirective({
27908 priority: 450,
27909 compile: function() {
27910 return {
27911 pre: function(scope, element, attrs) {
27912 scope.$eval(attrs.ngInit);
27913 }
27914 };
27915 }
27916});
27917
27918/**
27919 * @ngdoc directive
27920 * @name ngList
27921 *
27922 * @description
27923 * Text input that converts between a delimited string and an array of strings. The default
27924 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
27925 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
27926 *
27927 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
27928 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
27929 * list item is respected. This implies that the user of the directive is responsible for
27930 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
27931 * tab or newline character.
27932 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
27933 * when joining the list items back together) and whitespace around each list item is stripped
27934 * before it is added to the model.
27935 *
27936 * ### Example with Validation
27937 *
27938 * <example name="ngList-directive" module="listExample">
27939 * <file name="app.js">
27940 * angular.module('listExample', [])
27941 * .controller('ExampleController', ['$scope', function($scope) {
27942 * $scope.names = ['morpheus', 'neo', 'trinity'];
27943 * }]);
27944 * </file>
27945 * <file name="index.html">
27946 * <form name="myForm" ng-controller="ExampleController">
27947 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
27948 * <span role="alert">
27949 * <span class="error" ng-show="myForm.namesInput.$error.required">
27950 * Required!</span>
27951 * </span>
27952 * <br>
27953 * <tt>names = {{names}}</tt><br/>
27954 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
27955 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
27956 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
27957 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
27958 * </form>
27959 * </file>
27960 * <file name="protractor.js" type="protractor">
27961 * var listInput = element(by.model('names'));
27962 * var names = element(by.exactBinding('names'));
27963 * var valid = element(by.binding('myForm.namesInput.$valid'));
27964 * var error = element(by.css('span.error'));
27965 *
27966 * it('should initialize to model', function() {
27967 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
27968 * expect(valid.getText()).toContain('true');
27969 * expect(error.getCssValue('display')).toBe('none');
27970 * });
27971 *
27972 * it('should be invalid if empty', function() {
27973 * listInput.clear();
27974 * listInput.sendKeys('');
27975 *
27976 * expect(names.getText()).toContain('');
27977 * expect(valid.getText()).toContain('false');
27978 * expect(error.getCssValue('display')).not.toBe('none');
27979 * });
27980 * </file>
27981 * </example>
27982 *
27983 * ### Example - splitting on newline
27984 * <example name="ngList-directive-newlines">
27985 * <file name="index.html">
27986 * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
27987 * <pre>{{ list | json }}</pre>
27988 * </file>
27989 * <file name="protractor.js" type="protractor">
27990 * it("should split the text by newlines", function() {
27991 * var listInput = element(by.model('list'));
27992 * var output = element(by.binding('list | json'));
27993 * listInput.sendKeys('abc\ndef\nghi');
27994 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
27995 * });
27996 * </file>
27997 * </example>
27998 *
27999 * @element input
28000 * @param {string=} ngList optional delimiter that should be used to split the value.
28001 */
28002var ngListDirective = function() {
28003 return {
28004 restrict: 'A',
28005 priority: 100,
28006 require: 'ngModel',
28007 link: function(scope, element, attr, ctrl) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028008 var ngList = attr.ngList || ', ';
Ed Tanous904063f2017-03-02 16:48:24 -080028009 var trimValues = attr.ngTrim !== 'false';
28010 var separator = trimValues ? trim(ngList) : ngList;
28011
28012 var parse = function(viewValue) {
28013 // If the viewValue is invalid (say required but empty) it will be `undefined`
28014 if (isUndefined(viewValue)) return;
28015
28016 var list = [];
28017
28018 if (viewValue) {
28019 forEach(viewValue.split(separator), function(value) {
28020 if (value) list.push(trimValues ? trim(value) : value);
28021 });
28022 }
28023
28024 return list;
28025 };
28026
28027 ctrl.$parsers.push(parse);
28028 ctrl.$formatters.push(function(value) {
28029 if (isArray(value)) {
28030 return value.join(ngList);
28031 }
28032
28033 return undefined;
28034 });
28035
28036 // Override the standard $isEmpty because an empty array means the input is empty.
28037 ctrl.$isEmpty = function(value) {
28038 return !value || !value.length;
28039 };
28040 }
28041 };
28042};
28043
28044/* global VALID_CLASS: true,
28045 INVALID_CLASS: true,
28046 PRISTINE_CLASS: true,
28047 DIRTY_CLASS: true,
28048 UNTOUCHED_CLASS: true,
28049 TOUCHED_CLASS: true,
Ed Tanous4758d5b2017-06-06 15:28:13 -070028050 PENDING_CLASS: true,
28051 addSetValidityMethod: true,
28052 setupValidity: true,
28053 defaultModelOptions: false
Ed Tanous904063f2017-03-02 16:48:24 -080028054*/
28055
Ed Tanous4758d5b2017-06-06 15:28:13 -070028056
Ed Tanous904063f2017-03-02 16:48:24 -080028057var VALID_CLASS = 'ng-valid',
28058 INVALID_CLASS = 'ng-invalid',
28059 PRISTINE_CLASS = 'ng-pristine',
28060 DIRTY_CLASS = 'ng-dirty',
28061 UNTOUCHED_CLASS = 'ng-untouched',
28062 TOUCHED_CLASS = 'ng-touched',
Ed Tanous904063f2017-03-02 16:48:24 -080028063 EMPTY_CLASS = 'ng-empty',
28064 NOT_EMPTY_CLASS = 'ng-not-empty';
28065
28066var ngModelMinErr = minErr('ngModel');
28067
28068/**
28069 * @ngdoc type
28070 * @name ngModel.NgModelController
28071 *
28072 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
28073 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
28074 * is set.
Ed Tanous4758d5b2017-06-06 15:28:13 -070028075 *
Ed Tanous904063f2017-03-02 16:48:24 -080028076 * @property {*} $modelValue The value in the model that the control is bound to.
Ed Tanous4758d5b2017-06-06 15:28:13 -070028077 *
Ed Tanous904063f2017-03-02 16:48:24 -080028078 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
Ed Tanous4758d5b2017-06-06 15:28:13 -070028079 * the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue
28080 `$viewValue`} from the DOM, usually via user input.
28081 See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation.
28082 Note that the `$parsers` are not called when the bound ngModel expression changes programmatically.
Ed Tanous904063f2017-03-02 16:48:24 -080028083
Ed Tanous4758d5b2017-06-06 15:28:13 -070028084 The functions are called in array order, each passing
28085 its return value through to the next. The last return value is forwarded to the
28086 {@link ngModel.NgModelController#$validators `$validators`} collection.
Ed Tanous904063f2017-03-02 16:48:24 -080028087
Ed Tanous4758d5b2017-06-06 15:28:13 -070028088 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
28089 `$viewValue`}.
28090
28091 Returning `undefined` from a parser means a parse error occurred. In that case,
28092 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
28093 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
28094 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
28095
28096 This simple example shows a parser that would convert text input value to lowercase:
28097 * ```js
28098 * function parse(value) {
28099 * if (value) {
28100 * return value.toLowerCase();
28101 * }
28102 * }
28103 * ngModelController.$parsers.push(parse);
28104 * ```
Ed Tanous904063f2017-03-02 16:48:24 -080028105
28106 *
28107 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
Ed Tanous4758d5b2017-06-06 15:28:13 -070028108 the bound ngModel expression changes programmatically. The `$formatters` are not called when the
28109 value of the control is changed by user interaction.
28110
28111 Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue
28112 `$modelValue`} for display in the control.
28113
28114 The functions are called in reverse array order, each passing the value through to the
28115 next. The last return value is used as the actual DOM value.
28116
28117 This simple example shows a formatter that would convert the model value to uppercase:
28118
Ed Tanous904063f2017-03-02 16:48:24 -080028119 * ```js
Ed Tanous4758d5b2017-06-06 15:28:13 -070028120 * function format(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080028121 * if (value) {
28122 * return value.toUpperCase();
28123 * }
28124 * }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028125 * ngModel.$formatters.push(format);
Ed Tanous904063f2017-03-02 16:48:24 -080028126 * ```
28127 *
28128 * @property {Object.<string, function>} $validators A collection of validators that are applied
28129 * whenever the model value changes. The key value within the object refers to the name of the
28130 * validator while the function refers to the validation operation. The validation operation is
28131 * provided with the model value as an argument and must return a true or false value depending
28132 * on the response of that validation.
28133 *
28134 * ```js
28135 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
28136 * var value = modelValue || viewValue;
28137 * return /[0-9]+/.test(value) &&
28138 * /[a-z]+/.test(value) &&
28139 * /[A-Z]+/.test(value) &&
28140 * /\W+/.test(value);
28141 * };
28142 * ```
28143 *
28144 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
28145 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
28146 * is expected to return a promise when it is run during the model validation process. Once the promise
28147 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
28148 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
28149 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
28150 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
28151 * will only run once all synchronous validators have passed.
28152 *
28153 * Please note that if $http is used then it is important that the server returns a success HTTP response code
28154 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
28155 *
28156 * ```js
28157 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
28158 * var value = modelValue || viewValue;
28159 *
28160 * // Lookup user by username
28161 * return $http.get('/api/users/' + value).
28162 * then(function resolved() {
28163 * //username exists, this means validation fails
28164 * return $q.reject('exists');
28165 * }, function rejected() {
28166 * //username does not exist, therefore this validation passes
28167 * return true;
28168 * });
28169 * };
28170 * ```
28171 *
28172 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
28173 * view value has changed. It is called with no arguments, and its return value is ignored.
28174 * This can be used in place of additional $watches against the model value.
28175 *
28176 * @property {Object} $error An object hash with all failing validator ids as keys.
28177 * @property {Object} $pending An object hash with all pending validator ids as keys.
28178 *
28179 * @property {boolean} $untouched True if control has not lost focus yet.
28180 * @property {boolean} $touched True if control has lost focus.
28181 * @property {boolean} $pristine True if user has not interacted with the control yet.
28182 * @property {boolean} $dirty True if user has already interacted with the control.
28183 * @property {boolean} $valid True if there is no error.
28184 * @property {boolean} $invalid True if at least one error on the control.
28185 * @property {string} $name The name attribute of the control.
28186 *
28187 * @description
28188 *
28189 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
28190 * The controller contains services for data-binding, validation, CSS updates, and value formatting
28191 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
28192 * listening to DOM events.
28193 * Such DOM related logic should be provided by other directives which make use of
28194 * `NgModelController` for data-binding to control elements.
28195 * Angular provides this DOM logic for most {@link input `input`} elements.
28196 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
28197 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
28198 *
28199 * @example
28200 * ### Custom Control Example
28201 * This example shows how to use `NgModelController` with a custom control to achieve
28202 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
28203 * collaborate together to achieve the desired result.
28204 *
28205 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
28206 * contents be edited in place by the user.
28207 *
28208 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
28209 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
28210 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
28211 * that content using the `$sce` service.
28212 *
28213 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
28214 <file name="style.css">
28215 [contenteditable] {
28216 border: 1px solid black;
28217 background-color: white;
28218 min-height: 20px;
28219 }
28220
28221 .ng-invalid {
28222 border: 1px solid red;
28223 }
28224
28225 </file>
28226 <file name="script.js">
28227 angular.module('customControl', ['ngSanitize']).
28228 directive('contenteditable', ['$sce', function($sce) {
28229 return {
28230 restrict: 'A', // only activate on element attribute
28231 require: '?ngModel', // get a hold of NgModelController
28232 link: function(scope, element, attrs, ngModel) {
28233 if (!ngModel) return; // do nothing if no ng-model
28234
28235 // Specify how UI should be updated
28236 ngModel.$render = function() {
28237 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
28238 };
28239
28240 // Listen for change events to enable binding
28241 element.on('blur keyup change', function() {
28242 scope.$evalAsync(read);
28243 });
28244 read(); // initialize
28245
28246 // Write data to the model
28247 function read() {
28248 var html = element.html();
28249 // When we clear the content editable the browser leaves a <br> behind
28250 // If strip-br attribute is provided then we strip this out
Ed Tanous4758d5b2017-06-06 15:28:13 -070028251 if (attrs.stripBr && html === '<br>') {
Ed Tanous904063f2017-03-02 16:48:24 -080028252 html = '';
28253 }
28254 ngModel.$setViewValue(html);
28255 }
28256 }
28257 };
28258 }]);
28259 </file>
28260 <file name="index.html">
28261 <form name="myForm">
28262 <div contenteditable
28263 name="myWidget" ng-model="userContent"
28264 strip-br="true"
28265 required>Change me!</div>
28266 <span ng-show="myForm.myWidget.$error.required">Required!</span>
28267 <hr>
28268 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
28269 </form>
28270 </file>
28271 <file name="protractor.js" type="protractor">
28272 it('should data-bind and become invalid', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028273 if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') {
Ed Tanous904063f2017-03-02 16:48:24 -080028274 // SafariDriver can't handle contenteditable
28275 // and Firefox driver can't clear contenteditables very well
28276 return;
28277 }
28278 var contentEditable = element(by.css('[contenteditable]'));
28279 var content = 'Change me!';
28280
28281 expect(contentEditable.getText()).toEqual(content);
28282
28283 contentEditable.clear();
28284 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
28285 expect(contentEditable.getText()).toEqual('');
28286 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
28287 });
28288 </file>
28289 * </example>
28290 *
28291 *
28292 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028293NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate'];
28294function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) {
Ed Tanous904063f2017-03-02 16:48:24 -080028295 this.$viewValue = Number.NaN;
28296 this.$modelValue = Number.NaN;
28297 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
28298 this.$validators = {};
28299 this.$asyncValidators = {};
28300 this.$parsers = [];
28301 this.$formatters = [];
28302 this.$viewChangeListeners = [];
28303 this.$untouched = true;
28304 this.$touched = false;
28305 this.$pristine = true;
28306 this.$dirty = false;
28307 this.$valid = true;
28308 this.$invalid = false;
28309 this.$error = {}; // keep invalid keys here
28310 this.$$success = {}; // keep valid keys here
28311 this.$pending = undefined; // keep pending keys here
28312 this.$name = $interpolate($attr.name || '', false)($scope);
28313 this.$$parentForm = nullFormCtrl;
Ed Tanous4758d5b2017-06-06 15:28:13 -070028314 this.$options = defaultModelOptions;
Ed Tanous904063f2017-03-02 16:48:24 -080028315
Ed Tanous4758d5b2017-06-06 15:28:13 -070028316 this.$$parsedNgModel = $parse($attr.ngModel);
28317 this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
28318 this.$$ngModelGet = this.$$parsedNgModel;
28319 this.$$ngModelSet = this.$$parsedNgModelAssign;
28320 this.$$pendingDebounce = null;
28321 this.$$parserValid = undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080028322
Ed Tanous4758d5b2017-06-06 15:28:13 -070028323 this.$$currentValidationRunId = 0;
Ed Tanous904063f2017-03-02 16:48:24 -080028324
Ed Tanous4758d5b2017-06-06 15:28:13 -070028325 // https://github.com/angular/angular.js/issues/15833
28326 // Prevent `$$scope` from being iterated over by `copy` when NgModelController is deep watched
28327 Object.defineProperty(this, '$$scope', {value: $scope});
28328 this.$$attr = $attr;
28329 this.$$element = $element;
28330 this.$$animate = $animate;
28331 this.$$timeout = $timeout;
28332 this.$$parse = $parse;
28333 this.$$q = $q;
28334 this.$$exceptionHandler = $exceptionHandler;
28335
28336 setupValidity(this);
28337 setupModelWatcher(this);
28338}
28339
28340NgModelController.prototype = {
28341 $$initGetterSetters: function() {
28342 if (this.$options.getOption('getterSetter')) {
28343 var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'),
28344 invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)');
28345
28346 this.$$ngModelGet = function($scope) {
28347 var modelValue = this.$$parsedNgModel($scope);
Ed Tanous904063f2017-03-02 16:48:24 -080028348 if (isFunction(modelValue)) {
28349 modelValue = invokeModelGetter($scope);
28350 }
28351 return modelValue;
28352 };
Ed Tanous4758d5b2017-06-06 15:28:13 -070028353 this.$$ngModelSet = function($scope, newValue) {
28354 if (isFunction(this.$$parsedNgModel($scope))) {
Ed Tanous904063f2017-03-02 16:48:24 -080028355 invokeModelSetter($scope, {$$$p: newValue});
28356 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028357 this.$$parsedNgModelAssign($scope, newValue);
Ed Tanous904063f2017-03-02 16:48:24 -080028358 }
28359 };
Ed Tanous4758d5b2017-06-06 15:28:13 -070028360 } else if (!this.$$parsedNgModel.assign) {
28361 throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}',
28362 this.$$attr.ngModel, startingTag(this.$$element));
Ed Tanous904063f2017-03-02 16:48:24 -080028363 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028364 },
28365
Ed Tanous904063f2017-03-02 16:48:24 -080028366
28367 /**
28368 * @ngdoc method
28369 * @name ngModel.NgModelController#$render
28370 *
28371 * @description
28372 * Called when the view needs to be updated. It is expected that the user of the ng-model
28373 * directive will implement this method.
28374 *
28375 * The `$render()` method is invoked in the following situations:
28376 *
28377 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
28378 * committed value then `$render()` is called to update the input control.
28379 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
28380 * the `$viewValue` are different from last time.
28381 *
28382 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
28383 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue`
28384 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
28385 * invoked if you only change a property on the objects.
28386 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028387 $render: noop,
Ed Tanous904063f2017-03-02 16:48:24 -080028388
28389 /**
28390 * @ngdoc method
28391 * @name ngModel.NgModelController#$isEmpty
28392 *
28393 * @description
28394 * This is called when we need to determine if the value of an input is empty.
28395 *
28396 * For instance, the required directive does this to work out if the input has data or not.
28397 *
28398 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
28399 *
28400 * You can override this for input directives whose concept of being empty is different from the
28401 * default. The `checkboxInputType` directive does this because in its case a value of `false`
28402 * implies empty.
28403 *
28404 * @param {*} value The value of the input to check for emptiness.
28405 * @returns {boolean} True if `value` is "empty".
28406 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028407 $isEmpty: function(value) {
28408 // eslint-disable-next-line no-self-compare
Ed Tanous904063f2017-03-02 16:48:24 -080028409 return isUndefined(value) || value === '' || value === null || value !== value;
Ed Tanous4758d5b2017-06-06 15:28:13 -070028410 },
Ed Tanous904063f2017-03-02 16:48:24 -080028411
Ed Tanous4758d5b2017-06-06 15:28:13 -070028412 $$updateEmptyClasses: function(value) {
28413 if (this.$isEmpty(value)) {
28414 this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS);
28415 this.$$animate.addClass(this.$$element, EMPTY_CLASS);
Ed Tanous904063f2017-03-02 16:48:24 -080028416 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028417 this.$$animate.removeClass(this.$$element, EMPTY_CLASS);
28418 this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS);
Ed Tanous904063f2017-03-02 16:48:24 -080028419 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028420 },
Ed Tanous904063f2017-03-02 16:48:24 -080028421
28422 /**
28423 * @ngdoc method
28424 * @name ngModel.NgModelController#$setPristine
28425 *
28426 * @description
28427 * Sets the control to its pristine state.
28428 *
28429 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
28430 * state (`ng-pristine` class). A model is considered to be pristine when the control
28431 * has not been changed from when first compiled.
28432 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028433 $setPristine: function() {
28434 this.$dirty = false;
28435 this.$pristine = true;
28436 this.$$animate.removeClass(this.$$element, DIRTY_CLASS);
28437 this.$$animate.addClass(this.$$element, PRISTINE_CLASS);
28438 },
Ed Tanous904063f2017-03-02 16:48:24 -080028439
28440 /**
28441 * @ngdoc method
28442 * @name ngModel.NgModelController#$setDirty
28443 *
28444 * @description
28445 * Sets the control to its dirty state.
28446 *
28447 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
28448 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
28449 * from when first compiled.
28450 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028451 $setDirty: function() {
28452 this.$dirty = true;
28453 this.$pristine = false;
28454 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
28455 this.$$animate.addClass(this.$$element, DIRTY_CLASS);
28456 this.$$parentForm.$setDirty();
28457 },
Ed Tanous904063f2017-03-02 16:48:24 -080028458
28459 /**
28460 * @ngdoc method
28461 * @name ngModel.NgModelController#$setUntouched
28462 *
28463 * @description
28464 * Sets the control to its untouched state.
28465 *
28466 * This method can be called to remove the `ng-touched` class and set the control to its
28467 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
28468 * by default, however this function can be used to restore that state if the model has
28469 * already been touched by the user.
28470 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028471 $setUntouched: function() {
28472 this.$touched = false;
28473 this.$untouched = true;
28474 this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS);
28475 },
Ed Tanous904063f2017-03-02 16:48:24 -080028476
28477 /**
28478 * @ngdoc method
28479 * @name ngModel.NgModelController#$setTouched
28480 *
28481 * @description
28482 * Sets the control to its touched state.
28483 *
28484 * This method can be called to remove the `ng-untouched` class and set the control to its
28485 * touched state (`ng-touched` class). A model is considered to be touched when the user has
28486 * first focused the control element and then shifted focus away from the control (blur event).
28487 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028488 $setTouched: function() {
28489 this.$touched = true;
28490 this.$untouched = false;
28491 this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS);
28492 },
Ed Tanous904063f2017-03-02 16:48:24 -080028493
28494 /**
28495 * @ngdoc method
28496 * @name ngModel.NgModelController#$rollbackViewValue
28497 *
28498 * @description
28499 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
Ed Tanous4758d5b2017-06-06 15:28:13 -070028500 * which may be caused by a pending debounced event or because the input is waiting for some
Ed Tanous904063f2017-03-02 16:48:24 -080028501 * future event.
28502 *
28503 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that
Ed Tanous4758d5b2017-06-06 15:28:13 -070028504 * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of
28505 * sync with the ngModel's `$modelValue`.
Ed Tanous904063f2017-03-02 16:48:24 -080028506 *
28507 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update
28508 * and reset the input to the last committed view value.
28509 *
28510 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue`
28511 * programmatically before these debounced/future events have resolved/occurred, because Angular's
28512 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
28513 *
28514 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
28515 * input which may have such events pending. This is important in order to make sure that the
28516 * input field will be updated with the new model value and any pending operations are cancelled.
28517 *
28518 * <example name="ng-model-cancel-update" module="cancel-update-example">
28519 * <file name="app.js">
28520 * angular.module('cancel-update-example', [])
28521 *
28522 * .controller('CancelUpdateController', ['$scope', function($scope) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028523 * $scope.model = {value1: '', value2: ''};
Ed Tanous904063f2017-03-02 16:48:24 -080028524 *
28525 * $scope.setEmpty = function(e, value, rollback) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028526 * if (e.keyCode === 27) {
Ed Tanous904063f2017-03-02 16:48:24 -080028527 * e.preventDefault();
28528 * if (rollback) {
28529 * $scope.myForm[value].$rollbackViewValue();
28530 * }
28531 * $scope.model[value] = '';
28532 * }
28533 * };
28534 * }]);
28535 * </file>
28536 * <file name="index.html">
28537 * <div ng-controller="CancelUpdateController">
Ed Tanous4758d5b2017-06-06 15:28:13 -070028538 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should
28539 * empty them. Follow these steps and observe the difference:</p>
Ed Tanous904063f2017-03-02 16:48:24 -080028540 * <ol>
28541 * <li>Type something in the input. You will see that the model is not yet updated</li>
28542 * <li>Press the Escape key.
28543 * <ol>
28544 * <li> In the first example, nothing happens, because the model is already '', and no
28545 * update is detected. If you blur the input, the model will be set to the current view.
28546 * </li>
28547 * <li> In the second example, the pending update is cancelled, and the input is set back
28548 * to the last committed view value (''). Blurring the input does nothing.
28549 * </li>
28550 * </ol>
28551 * </li>
28552 * </ol>
28553 *
28554 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
28555 * <div>
Ed Tanous4758d5b2017-06-06 15:28:13 -070028556 * <p id="inputDescription1">Without $rollbackViewValue():</p>
28557 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1"
28558 * ng-keydown="setEmpty($event, 'value1')">
28559 * value1: "{{ model.value1 }}"
Ed Tanous904063f2017-03-02 16:48:24 -080028560 * </div>
28561 *
28562 * <div>
Ed Tanous4758d5b2017-06-06 15:28:13 -070028563 * <p id="inputDescription2">With $rollbackViewValue():</p>
28564 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2"
28565 * ng-keydown="setEmpty($event, 'value2', true)">
28566 * value2: "{{ model.value2 }}"
Ed Tanous904063f2017-03-02 16:48:24 -080028567 * </div>
28568 * </form>
28569 * </div>
28570 * </file>
28571 <file name="style.css">
28572 div {
28573 display: table-cell;
28574 }
28575 div:nth-child(1) {
28576 padding-right: 30px;
28577 }
28578
28579 </file>
28580 * </example>
28581 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028582 $rollbackViewValue: function() {
28583 this.$$timeout.cancel(this.$$pendingDebounce);
28584 this.$viewValue = this.$$lastCommittedViewValue;
28585 this.$render();
28586 },
Ed Tanous904063f2017-03-02 16:48:24 -080028587
28588 /**
28589 * @ngdoc method
28590 * @name ngModel.NgModelController#$validate
28591 *
28592 * @description
28593 * Runs each of the registered validators (first synchronous validators and then
28594 * asynchronous validators).
28595 * If the validity changes to invalid, the model will be set to `undefined`,
28596 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
28597 * If the validity changes to valid, it will set the model to the last available valid
28598 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
28599 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028600 $validate: function() {
Ed Tanous904063f2017-03-02 16:48:24 -080028601 // ignore $validate before model is initialized
Ed Tanous4758d5b2017-06-06 15:28:13 -070028602 if (isNumberNaN(this.$modelValue)) {
Ed Tanous904063f2017-03-02 16:48:24 -080028603 return;
28604 }
28605
Ed Tanous4758d5b2017-06-06 15:28:13 -070028606 var viewValue = this.$$lastCommittedViewValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028607 // Note: we use the $$rawModelValue as $modelValue might have been
28608 // set to undefined during a view -> model update that found validation
28609 // errors. We can't parse the view here, since that could change
28610 // the model although neither viewValue nor the model on the scope changed
Ed Tanous4758d5b2017-06-06 15:28:13 -070028611 var modelValue = this.$$rawModelValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028612
Ed Tanous4758d5b2017-06-06 15:28:13 -070028613 var prevValid = this.$valid;
28614 var prevModelValue = this.$modelValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028615
Ed Tanous4758d5b2017-06-06 15:28:13 -070028616 var allowInvalid = this.$options.getOption('allowInvalid');
Ed Tanous904063f2017-03-02 16:48:24 -080028617
Ed Tanous4758d5b2017-06-06 15:28:13 -070028618 var that = this;
28619 this.$$runValidators(modelValue, viewValue, function(allValid) {
Ed Tanous904063f2017-03-02 16:48:24 -080028620 // If there was no change in validity, don't update the model
28621 // This prevents changing an invalid modelValue to undefined
28622 if (!allowInvalid && prevValid !== allValid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028623 // Note: Don't check this.$valid here, as we could have
Ed Tanous904063f2017-03-02 16:48:24 -080028624 // external validators (e.g. calculated on the server),
28625 // that just call $setValidity and need the model value
28626 // to calculate their validity.
Ed Tanous4758d5b2017-06-06 15:28:13 -070028627 that.$modelValue = allValid ? modelValue : undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080028628
Ed Tanous4758d5b2017-06-06 15:28:13 -070028629 if (that.$modelValue !== prevModelValue) {
28630 that.$$writeModelToScope();
Ed Tanous904063f2017-03-02 16:48:24 -080028631 }
28632 }
28633 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070028634 },
Ed Tanous904063f2017-03-02 16:48:24 -080028635
Ed Tanous4758d5b2017-06-06 15:28:13 -070028636 $$runValidators: function(modelValue, viewValue, doneCallback) {
28637 this.$$currentValidationRunId++;
28638 var localValidationRunId = this.$$currentValidationRunId;
28639 var that = this;
Ed Tanous904063f2017-03-02 16:48:24 -080028640
28641 // check parser error
28642 if (!processParseErrors()) {
28643 validationDone(false);
28644 return;
28645 }
28646 if (!processSyncValidators()) {
28647 validationDone(false);
28648 return;
28649 }
28650 processAsyncValidators();
28651
28652 function processParseErrors() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028653 var errorKey = that.$$parserName || 'parse';
28654 if (isUndefined(that.$$parserValid)) {
Ed Tanous904063f2017-03-02 16:48:24 -080028655 setValidity(errorKey, null);
28656 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028657 if (!that.$$parserValid) {
28658 forEach(that.$validators, function(v, name) {
Ed Tanous904063f2017-03-02 16:48:24 -080028659 setValidity(name, null);
28660 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070028661 forEach(that.$asyncValidators, function(v, name) {
Ed Tanous904063f2017-03-02 16:48:24 -080028662 setValidity(name, null);
28663 });
28664 }
28665 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
Ed Tanous4758d5b2017-06-06 15:28:13 -070028666 setValidity(errorKey, that.$$parserValid);
28667 return that.$$parserValid;
Ed Tanous904063f2017-03-02 16:48:24 -080028668 }
28669 return true;
28670 }
28671
28672 function processSyncValidators() {
28673 var syncValidatorsValid = true;
Ed Tanous4758d5b2017-06-06 15:28:13 -070028674 forEach(that.$validators, function(validator, name) {
28675 var result = Boolean(validator(modelValue, viewValue));
Ed Tanous904063f2017-03-02 16:48:24 -080028676 syncValidatorsValid = syncValidatorsValid && result;
28677 setValidity(name, result);
28678 });
28679 if (!syncValidatorsValid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028680 forEach(that.$asyncValidators, function(v, name) {
Ed Tanous904063f2017-03-02 16:48:24 -080028681 setValidity(name, null);
28682 });
28683 return false;
28684 }
28685 return true;
28686 }
28687
28688 function processAsyncValidators() {
28689 var validatorPromises = [];
28690 var allValid = true;
Ed Tanous4758d5b2017-06-06 15:28:13 -070028691 forEach(that.$asyncValidators, function(validator, name) {
Ed Tanous904063f2017-03-02 16:48:24 -080028692 var promise = validator(modelValue, viewValue);
28693 if (!isPromiseLike(promise)) {
28694 throw ngModelMinErr('nopromise',
Ed Tanous4758d5b2017-06-06 15:28:13 -070028695 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise);
Ed Tanous904063f2017-03-02 16:48:24 -080028696 }
28697 setValidity(name, undefined);
28698 validatorPromises.push(promise.then(function() {
28699 setValidity(name, true);
28700 }, function() {
28701 allValid = false;
28702 setValidity(name, false);
28703 }));
28704 });
28705 if (!validatorPromises.length) {
28706 validationDone(true);
28707 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028708 that.$$q.all(validatorPromises).then(function() {
Ed Tanous904063f2017-03-02 16:48:24 -080028709 validationDone(allValid);
28710 }, noop);
28711 }
28712 }
28713
28714 function setValidity(name, isValid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028715 if (localValidationRunId === that.$$currentValidationRunId) {
28716 that.$setValidity(name, isValid);
Ed Tanous904063f2017-03-02 16:48:24 -080028717 }
28718 }
28719
28720 function validationDone(allValid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028721 if (localValidationRunId === that.$$currentValidationRunId) {
Ed Tanous904063f2017-03-02 16:48:24 -080028722
28723 doneCallback(allValid);
28724 }
28725 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028726 },
Ed Tanous904063f2017-03-02 16:48:24 -080028727
28728 /**
28729 * @ngdoc method
28730 * @name ngModel.NgModelController#$commitViewValue
28731 *
28732 * @description
28733 * Commit a pending update to the `$modelValue`.
28734 *
28735 * Updates may be pending by a debounced event or because the input is waiting for a some future
28736 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
28737 * usually handles calling this in response to input events.
28738 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028739 $commitViewValue: function() {
28740 var viewValue = this.$viewValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028741
Ed Tanous4758d5b2017-06-06 15:28:13 -070028742 this.$$timeout.cancel(this.$$pendingDebounce);
Ed Tanous904063f2017-03-02 16:48:24 -080028743
28744 // If the view value has not changed then we should just exit, except in the case where there is
28745 // a native validator on the element. In this case the validation state may have changed even though
28746 // the viewValue has stayed empty.
Ed Tanous4758d5b2017-06-06 15:28:13 -070028747 if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) {
Ed Tanous904063f2017-03-02 16:48:24 -080028748 return;
28749 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028750 this.$$updateEmptyClasses(viewValue);
28751 this.$$lastCommittedViewValue = viewValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028752
28753 // change to dirty
Ed Tanous4758d5b2017-06-06 15:28:13 -070028754 if (this.$pristine) {
Ed Tanous904063f2017-03-02 16:48:24 -080028755 this.$setDirty();
28756 }
28757 this.$$parseAndValidate();
Ed Tanous4758d5b2017-06-06 15:28:13 -070028758 },
Ed Tanous904063f2017-03-02 16:48:24 -080028759
Ed Tanous4758d5b2017-06-06 15:28:13 -070028760 $$parseAndValidate: function() {
28761 var viewValue = this.$$lastCommittedViewValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028762 var modelValue = viewValue;
Ed Tanous4758d5b2017-06-06 15:28:13 -070028763 var that = this;
Ed Tanous904063f2017-03-02 16:48:24 -080028764
Ed Tanous4758d5b2017-06-06 15:28:13 -070028765 this.$$parserValid = isUndefined(modelValue) ? undefined : true;
28766
28767 if (this.$$parserValid) {
28768 for (var i = 0; i < this.$parsers.length; i++) {
28769 modelValue = this.$parsers[i](modelValue);
Ed Tanous904063f2017-03-02 16:48:24 -080028770 if (isUndefined(modelValue)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028771 this.$$parserValid = false;
Ed Tanous904063f2017-03-02 16:48:24 -080028772 break;
28773 }
28774 }
28775 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028776 if (isNumberNaN(this.$modelValue)) {
28777 // this.$modelValue has not been touched yet...
28778 this.$modelValue = this.$$ngModelGet(this.$$scope);
Ed Tanous904063f2017-03-02 16:48:24 -080028779 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028780 var prevModelValue = this.$modelValue;
28781 var allowInvalid = this.$options.getOption('allowInvalid');
28782 this.$$rawModelValue = modelValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028783
28784 if (allowInvalid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028785 this.$modelValue = modelValue;
Ed Tanous904063f2017-03-02 16:48:24 -080028786 writeToModelIfNeeded();
28787 }
28788
28789 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
28790 // This can happen if e.g. $setViewValue is called from inside a parser
Ed Tanous4758d5b2017-06-06 15:28:13 -070028791 this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) {
Ed Tanous904063f2017-03-02 16:48:24 -080028792 if (!allowInvalid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028793 // Note: Don't check this.$valid here, as we could have
Ed Tanous904063f2017-03-02 16:48:24 -080028794 // external validators (e.g. calculated on the server),
28795 // that just call $setValidity and need the model value
28796 // to calculate their validity.
Ed Tanous4758d5b2017-06-06 15:28:13 -070028797 that.$modelValue = allValid ? modelValue : undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080028798 writeToModelIfNeeded();
28799 }
28800 });
28801
28802 function writeToModelIfNeeded() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028803 if (that.$modelValue !== prevModelValue) {
28804 that.$$writeModelToScope();
Ed Tanous904063f2017-03-02 16:48:24 -080028805 }
28806 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028807 },
Ed Tanous904063f2017-03-02 16:48:24 -080028808
Ed Tanous4758d5b2017-06-06 15:28:13 -070028809 $$writeModelToScope: function() {
28810 this.$$ngModelSet(this.$$scope, this.$modelValue);
28811 forEach(this.$viewChangeListeners, function(listener) {
Ed Tanous904063f2017-03-02 16:48:24 -080028812 try {
28813 listener();
28814 } catch (e) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028815 // eslint-disable-next-line no-invalid-this
28816 this.$$exceptionHandler(e);
Ed Tanous904063f2017-03-02 16:48:24 -080028817 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028818 }, this);
28819 },
Ed Tanous904063f2017-03-02 16:48:24 -080028820
28821 /**
28822 * @ngdoc method
28823 * @name ngModel.NgModelController#$setViewValue
28824 *
28825 * @description
28826 * Update the view value.
28827 *
28828 * This method should be called when a control wants to change the view value; typically,
28829 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
28830 * directive calls it when the value of the input changes and {@link ng.directive:select select}
28831 * calls it when an option is selected.
28832 *
28833 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
28834 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
Ed Tanous4758d5b2017-06-06 15:28:13 -070028835 * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and
28836 * `$asyncValidators` are called and the value is applied to `$modelValue`.
28837 * Finally, the value is set to the **expression** specified in the `ng-model` attribute and
28838 * all the registered change listeners, in the `$viewChangeListeners` list are called.
Ed Tanous904063f2017-03-02 16:48:24 -080028839 *
28840 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
28841 * and the `default` trigger is not listed, all those actions will remain pending until one of the
28842 * `updateOn` events is triggered on the DOM element.
28843 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
28844 * directive is used with a custom debounce for this particular event.
28845 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
28846 * is specified, once the timer runs out.
28847 *
28848 * When used with standard inputs, the view value will always be a string (which is in some cases
28849 * parsed into another type, such as a `Date` object for `input[date]`.)
28850 * However, custom controls might also pass objects to this method. In this case, we should make
28851 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
28852 * perform a deep watch of objects, it only looks for a change of identity. If you only change
28853 * the property of the object then ngModel will not realize that the object has changed and
28854 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
28855 * not change properties of the copy once it has been passed to `$setViewValue`.
28856 * Otherwise you may cause the model value on the scope to change incorrectly.
28857 *
28858 * <div class="alert alert-info">
28859 * In any case, the value passed to the method should always reflect the current value
28860 * of the control. For example, if you are calling `$setViewValue` for an input element,
28861 * you should pass the input DOM value. Otherwise, the control and the scope model become
28862 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
28863 * the control's DOM value in any way. If we want to change the control's DOM value
28864 * programmatically, we should update the `ngModel` scope expression. Its new value will be
28865 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
28866 * to update the DOM, and finally call `$validate` on it.
28867 * </div>
28868 *
28869 * @param {*} value value from the view.
28870 * @param {string} trigger Event that triggered the update.
28871 */
Ed Tanous4758d5b2017-06-06 15:28:13 -070028872 $setViewValue: function(value, trigger) {
28873 this.$viewValue = value;
28874 if (this.$options.getOption('updateOnDefault')) {
28875 this.$$debounceViewValueCommit(trigger);
Ed Tanous904063f2017-03-02 16:48:24 -080028876 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028877 },
Ed Tanous904063f2017-03-02 16:48:24 -080028878
Ed Tanous4758d5b2017-06-06 15:28:13 -070028879 $$debounceViewValueCommit: function(trigger) {
28880 var debounceDelay = this.$options.getOption('debounce');
Ed Tanous904063f2017-03-02 16:48:24 -080028881
Ed Tanous4758d5b2017-06-06 15:28:13 -070028882 if (isNumber(debounceDelay[trigger])) {
28883 debounceDelay = debounceDelay[trigger];
28884 } else if (isNumber(debounceDelay['default'])) {
28885 debounceDelay = debounceDelay['default'];
Ed Tanous904063f2017-03-02 16:48:24 -080028886 }
28887
Ed Tanous4758d5b2017-06-06 15:28:13 -070028888 this.$$timeout.cancel(this.$$pendingDebounce);
28889 var that = this;
28890 if (debounceDelay > 0) { // this fails if debounceDelay is an object
28891 this.$$pendingDebounce = this.$$timeout(function() {
28892 that.$commitViewValue();
Ed Tanous904063f2017-03-02 16:48:24 -080028893 }, debounceDelay);
Ed Tanous4758d5b2017-06-06 15:28:13 -070028894 } else if (this.$$scope.$root.$$phase) {
28895 this.$commitViewValue();
Ed Tanous904063f2017-03-02 16:48:24 -080028896 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070028897 this.$$scope.$apply(function() {
28898 that.$commitViewValue();
Ed Tanous904063f2017-03-02 16:48:24 -080028899 });
28900 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070028901 },
Ed Tanous904063f2017-03-02 16:48:24 -080028902
Ed Tanous4758d5b2017-06-06 15:28:13 -070028903 /**
28904 * @ngdoc method
28905 *
28906 * @name ngModel.NgModelController#$overrideModelOptions
28907 *
28908 * @description
28909 *
28910 * Override the current model options settings programmatically.
28911 *
28912 * The previous `ModelOptions` value will not be modified. Instead, a
28913 * new `ModelOptions` object will inherit from the previous one overriding
28914 * or inheriting settings that are defined in the given parameter.
28915 *
28916 * See {@link ngModelOptions} for information about what options can be specified
28917 * and how model option inheritance works.
28918 *
28919 * @param {Object} options a hash of settings to override the previous options
28920 *
28921 */
28922 $overrideModelOptions: function(options) {
28923 this.$options = this.$options.createChild(options);
28924 }
28925};
28926
28927function setupModelWatcher(ctrl) {
Ed Tanous904063f2017-03-02 16:48:24 -080028928 // model -> value
28929 // Note: we cannot use a normal scope.$watch as we want to detect the following:
28930 // 1. scope value is 'a'
28931 // 2. user enters 'b'
28932 // 3. ng-change kicks in and reverts scope value to 'a'
28933 // -> scope value did not change since the last digest as
28934 // ng-change executes in apply phase
28935 // 4. view should be changed back to 'a'
Ed Tanous4758d5b2017-06-06 15:28:13 -070028936 ctrl.$$scope.$watch(function ngModelWatch(scope) {
28937 var modelValue = ctrl.$$ngModelGet(scope);
Ed Tanous904063f2017-03-02 16:48:24 -080028938
28939 // if scope model value and ngModel value are out of sync
28940 // TODO(perf): why not move this to the action fn?
28941 if (modelValue !== ctrl.$modelValue &&
28942 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
Ed Tanous4758d5b2017-06-06 15:28:13 -070028943 // eslint-disable-next-line no-self-compare
Ed Tanous904063f2017-03-02 16:48:24 -080028944 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
28945 ) {
28946 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
Ed Tanous4758d5b2017-06-06 15:28:13 -070028947 ctrl.$$parserValid = undefined;
Ed Tanous904063f2017-03-02 16:48:24 -080028948
28949 var formatters = ctrl.$formatters,
28950 idx = formatters.length;
28951
28952 var viewValue = modelValue;
28953 while (idx--) {
28954 viewValue = formatters[idx](viewValue);
28955 }
28956 if (ctrl.$viewValue !== viewValue) {
28957 ctrl.$$updateEmptyClasses(viewValue);
28958 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
28959 ctrl.$render();
28960
Ed Tanous4758d5b2017-06-06 15:28:13 -070028961 // It is possible that model and view value have been updated during render
28962 ctrl.$$runValidators(ctrl.$modelValue, ctrl.$viewValue, noop);
Ed Tanous904063f2017-03-02 16:48:24 -080028963 }
28964 }
28965
28966 return modelValue;
28967 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070028968}
28969
28970/**
28971 * @ngdoc method
28972 * @name ngModel.NgModelController#$setValidity
28973 *
28974 * @description
28975 * Change the validity state, and notify the form.
28976 *
28977 * This method can be called within $parsers/$formatters or a custom validation implementation.
28978 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
28979 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
28980 *
28981 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
28982 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
28983 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
28984 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
28985 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
28986 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
28987 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
28988 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
28989 * Skipped is used by Angular when validators do not run because of parse errors and
28990 * when `$asyncValidators` do not run because any of the `$validators` failed.
28991 */
28992addSetValidityMethod({
28993 clazz: NgModelController,
28994 set: function(object, property) {
28995 object[property] = true;
28996 },
28997 unset: function(object, property) {
28998 delete object[property];
28999 }
29000});
Ed Tanous904063f2017-03-02 16:48:24 -080029001
29002
29003/**
29004 * @ngdoc directive
29005 * @name ngModel
29006 *
29007 * @element input
29008 * @priority 1
29009 *
29010 * @description
29011 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
29012 * property on the scope using {@link ngModel.NgModelController NgModelController},
29013 * which is created and exposed by this directive.
29014 *
29015 * `ngModel` is responsible for:
29016 *
29017 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
29018 * require.
29019 * - Providing validation behavior (i.e. required, number, email, url).
29020 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
29021 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
29022 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
29023 * - Registering the control with its parent {@link ng.directive:form form}.
29024 *
29025 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
29026 * current scope. If the property doesn't already exist on this scope, it will be created
29027 * implicitly and added to the scope.
29028 *
29029 * For best practices on using `ngModel`, see:
29030 *
29031 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
29032 *
29033 * For basic examples, how to use `ngModel`, see:
29034 *
29035 * - {@link ng.directive:input input}
29036 * - {@link input[text] text}
29037 * - {@link input[checkbox] checkbox}
29038 * - {@link input[radio] radio}
29039 * - {@link input[number] number}
29040 * - {@link input[email] email}
29041 * - {@link input[url] url}
29042 * - {@link input[date] date}
29043 * - {@link input[datetime-local] datetime-local}
29044 * - {@link input[time] time}
29045 * - {@link input[month] month}
29046 * - {@link input[week] week}
29047 * - {@link ng.directive:select select}
29048 * - {@link ng.directive:textarea textarea}
29049 *
29050 * # Complex Models (objects or collections)
29051 *
29052 * By default, `ngModel` watches the model by reference, not value. This is important to know when
29053 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the
29054 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered.
29055 *
29056 * The model must be assigned an entirely new object or collection before a re-rendering will occur.
29057 *
29058 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression
29059 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or
29060 * if the select is given the `multiple` attribute.
29061 *
29062 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the
29063 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still
29064 * not trigger a re-rendering of the model.
29065 *
29066 * # CSS classes
29067 * The following CSS classes are added and removed on the associated input/select/textarea element
29068 * depending on the validity of the model.
29069 *
29070 * - `ng-valid`: the model is valid
29071 * - `ng-invalid`: the model is invalid
29072 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
29073 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
29074 * - `ng-pristine`: the control hasn't been interacted with yet
29075 * - `ng-dirty`: the control has been interacted with
29076 * - `ng-touched`: the control has been blurred
29077 * - `ng-untouched`: the control hasn't been blurred
29078 * - `ng-pending`: any `$asyncValidators` are unfulfilled
29079 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
29080 * by the {@link ngModel.NgModelController#$isEmpty} method
29081 * - `ng-not-empty`: the view contains a non-empty value
29082 *
29083 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
29084 *
29085 * ## Animation Hooks
29086 *
29087 * Animations within models are triggered when any of the associated CSS classes are added and removed
29088 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
29089 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
29090 * The animations that are triggered within ngModel are similar to how they work in ngClass and
29091 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
29092 *
29093 * The following example shows a simple way to utilize CSS transitions to style an input element
29094 * that has been rendered as invalid after it has been validated:
29095 *
29096 * <pre>
29097 * //be sure to include ngAnimate as a module to hook into more
29098 * //advanced animations
29099 * .my-input {
29100 * transition:0.5s linear all;
29101 * background: white;
29102 * }
29103 * .my-input.ng-invalid {
29104 * background: red;
29105 * color:white;
29106 * }
29107 * </pre>
29108 *
29109 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070029110 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample" name="ng-model">
Ed Tanous904063f2017-03-02 16:48:24 -080029111 <file name="index.html">
29112 <script>
29113 angular.module('inputExample', [])
29114 .controller('ExampleController', ['$scope', function($scope) {
29115 $scope.val = '1';
29116 }]);
29117 </script>
29118 <style>
29119 .my-input {
29120 transition:all linear 0.5s;
29121 background: transparent;
29122 }
29123 .my-input.ng-invalid {
29124 color:white;
29125 background: red;
29126 }
29127 </style>
29128 <p id="inputDescription">
29129 Update input to see transitions when valid/invalid.
29130 Integer is a valid value.
29131 </p>
29132 <form name="testForm" ng-controller="ExampleController">
29133 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
29134 aria-describedby="inputDescription" />
29135 </form>
29136 </file>
29137 * </example>
29138 *
29139 * ## Binding to a getter/setter
29140 *
29141 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
29142 * function that returns a representation of the model when called with zero arguments, and sets
29143 * the internal state of a model when called with an argument. It's sometimes useful to use this
29144 * for models that have an internal representation that's different from what the model exposes
29145 * to the view.
29146 *
29147 * <div class="alert alert-success">
29148 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
29149 * frequently than other parts of your code.
29150 * </div>
29151 *
29152 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
29153 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
29154 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
29155 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
29156 *
29157 * The following example shows how to use `ngModel` with a getter/setter:
29158 *
29159 * @example
29160 * <example name="ngModel-getter-setter" module="getterSetterExample">
29161 <file name="index.html">
29162 <div ng-controller="ExampleController">
29163 <form name="userForm">
29164 <label>Name:
29165 <input type="text" name="userName"
29166 ng-model="user.name"
29167 ng-model-options="{ getterSetter: true }" />
29168 </label>
29169 </form>
29170 <pre>user.name = <span ng-bind="user.name()"></span></pre>
29171 </div>
29172 </file>
29173 <file name="app.js">
29174 angular.module('getterSetterExample', [])
29175 .controller('ExampleController', ['$scope', function($scope) {
29176 var _name = 'Brian';
29177 $scope.user = {
29178 name: function(newName) {
29179 // Note that newName can be undefined for two reasons:
29180 // 1. Because it is called as a getter and thus called with no arguments
29181 // 2. Because the property should actually be set to undefined. This happens e.g. if the
29182 // input is invalid
29183 return arguments.length ? (_name = newName) : _name;
29184 }
29185 };
29186 }]);
29187 </file>
29188 * </example>
29189 */
29190var ngModelDirective = ['$rootScope', function($rootScope) {
29191 return {
29192 restrict: 'A',
29193 require: ['ngModel', '^?form', '^?ngModelOptions'],
29194 controller: NgModelController,
29195 // Prelink needs to run before any input directive
29196 // so that we can set the NgModelOptions in NgModelController
29197 // before anyone else uses it.
29198 priority: 1,
29199 compile: function ngModelCompile(element) {
29200 // Setup initial state of the control
29201 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
29202
29203 return {
29204 pre: function ngModelPreLink(scope, element, attr, ctrls) {
29205 var modelCtrl = ctrls[0],
Ed Tanous4758d5b2017-06-06 15:28:13 -070029206 formCtrl = ctrls[1] || modelCtrl.$$parentForm,
29207 optionsCtrl = ctrls[2];
Ed Tanous904063f2017-03-02 16:48:24 -080029208
Ed Tanous4758d5b2017-06-06 15:28:13 -070029209 if (optionsCtrl) {
29210 modelCtrl.$options = optionsCtrl.$options;
29211 }
29212
29213 modelCtrl.$$initGetterSetters();
Ed Tanous904063f2017-03-02 16:48:24 -080029214
29215 // notify others, especially parent forms
29216 formCtrl.$addControl(modelCtrl);
29217
29218 attr.$observe('name', function(newValue) {
29219 if (modelCtrl.$name !== newValue) {
29220 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
29221 }
29222 });
29223
29224 scope.$on('$destroy', function() {
29225 modelCtrl.$$parentForm.$removeControl(modelCtrl);
29226 });
29227 },
29228 post: function ngModelPostLink(scope, element, attr, ctrls) {
29229 var modelCtrl = ctrls[0];
Ed Tanous4758d5b2017-06-06 15:28:13 -070029230 if (modelCtrl.$options.getOption('updateOn')) {
29231 element.on(modelCtrl.$options.getOption('updateOn'), function(ev) {
Ed Tanous904063f2017-03-02 16:48:24 -080029232 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
29233 });
29234 }
29235
Ed Tanous4758d5b2017-06-06 15:28:13 -070029236 function setTouched() {
29237 modelCtrl.$setTouched();
29238 }
29239
Ed Tanous904063f2017-03-02 16:48:24 -080029240 element.on('blur', function() {
29241 if (modelCtrl.$touched) return;
29242
29243 if ($rootScope.$$phase) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070029244 scope.$evalAsync(setTouched);
Ed Tanous904063f2017-03-02 16:48:24 -080029245 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070029246 scope.$apply(setTouched);
Ed Tanous904063f2017-03-02 16:48:24 -080029247 }
29248 });
29249 }
29250 };
29251 }
29252 };
29253}];
29254
Ed Tanous4758d5b2017-06-06 15:28:13 -070029255/* exported defaultModelOptions */
29256var defaultModelOptions;
Ed Tanous904063f2017-03-02 16:48:24 -080029257var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
29258
29259/**
Ed Tanous4758d5b2017-06-06 15:28:13 -070029260 * @ngdoc type
29261 * @name ModelOptions
29262 * @description
29263 * A container for the options set by the {@link ngModelOptions} directive
29264 */
29265function ModelOptions(options) {
29266 this.$$options = options;
29267}
29268
29269ModelOptions.prototype = {
29270
29271 /**
29272 * @ngdoc method
29273 * @name ModelOptions#getOption
29274 * @param {string} name the name of the option to retrieve
29275 * @returns {*} the value of the option
29276 * @description
29277 * Returns the value of the given option
29278 */
29279 getOption: function(name) {
29280 return this.$$options[name];
29281 },
29282
29283 /**
29284 * @ngdoc method
29285 * @name ModelOptions#createChild
29286 * @param {Object} options a hash of options for the new child that will override the parent's options
29287 * @return {ModelOptions} a new `ModelOptions` object initialized with the given options.
29288 */
29289 createChild: function(options) {
29290 var inheritAll = false;
29291
29292 // make a shallow copy
29293 options = extend({}, options);
29294
29295 // Inherit options from the parent if specified by the value `"$inherit"`
29296 forEach(options, /* @this */ function(option, key) {
29297 if (option === '$inherit') {
29298 if (key === '*') {
29299 inheritAll = true;
29300 } else {
29301 options[key] = this.$$options[key];
29302 // `updateOn` is special so we must also inherit the `updateOnDefault` option
29303 if (key === 'updateOn') {
29304 options.updateOnDefault = this.$$options.updateOnDefault;
29305 }
29306 }
29307 } else {
29308 if (key === 'updateOn') {
29309 // If the `updateOn` property contains the `default` event then we have to remove
29310 // it from the event list and set the `updateOnDefault` flag.
29311 options.updateOnDefault = false;
29312 options[key] = trim(option.replace(DEFAULT_REGEXP, function() {
29313 options.updateOnDefault = true;
29314 return ' ';
29315 }));
29316 }
29317 }
29318 }, this);
29319
29320 if (inheritAll) {
29321 // We have a property of the form: `"*": "$inherit"`
29322 delete options['*'];
29323 defaults(options, this.$$options);
29324 }
29325
29326 // Finally add in any missing defaults
29327 defaults(options, defaultModelOptions.$$options);
29328
29329 return new ModelOptions(options);
29330 }
29331};
29332
29333
29334defaultModelOptions = new ModelOptions({
29335 updateOn: '',
29336 updateOnDefault: true,
29337 debounce: 0,
29338 getterSetter: false,
29339 allowInvalid: false,
29340 timezone: null
29341});
29342
29343
29344/**
Ed Tanous904063f2017-03-02 16:48:24 -080029345 * @ngdoc directive
29346 * @name ngModelOptions
29347 *
29348 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070029349 * This directive allows you to modify the behaviour of {@link ngModel} directives within your
29350 * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel}
29351 * directives will use the options of their nearest `ngModelOptions` ancestor.
29352 *
29353 * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as
29354 * an Angular expression. This expression should evaluate to an object, whose properties contain
29355 * the settings. For example: `<div "ng-model-options"="{ debounce: 100 }"`.
29356 *
29357 * ## Inheriting Options
29358 *
29359 * You can specify that an `ngModelOptions` setting should be inherited from a parent `ngModelOptions`
29360 * directive by giving it the value of `"$inherit"`.
29361 * Then it will inherit that setting from the first `ngModelOptions` directive found by traversing up the
29362 * DOM tree. If there is no ancestor element containing an `ngModelOptions` directive then default settings
29363 * will be used.
29364 *
29365 * For example given the following fragment of HTML
29366 *
29367 *
29368 * ```html
29369 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }">
29370 * <form ng-model-options="{ updateOn: 'blur', allowInvalid: '$inherit' }">
29371 * <input ng-model-options="{ updateOn: 'default', allowInvalid: '$inherit' }" />
29372 * </form>
29373 * </div>
29374 * ```
29375 *
29376 * the `input` element will have the following settings
29377 *
29378 * ```js
29379 * { allowInvalid: true, updateOn: 'default', debounce: 0 }
29380 * ```
29381 *
29382 * Notice that the `debounce` setting was not inherited and used the default value instead.
29383 *
29384 * You can specify that all undefined settings are automatically inherited from an ancestor by
29385 * including a property with key of `"*"` and value of `"$inherit"`.
29386 *
29387 * For example given the following fragment of HTML
29388 *
29389 *
29390 * ```html
29391 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }">
29392 * <form ng-model-options="{ updateOn: 'blur', "*": '$inherit' }">
29393 * <input ng-model-options="{ updateOn: 'default', "*": '$inherit' }" />
29394 * </form>
29395 * </div>
29396 * ```
29397 *
29398 * the `input` element will have the following settings
29399 *
29400 * ```js
29401 * { allowInvalid: true, updateOn: 'default', debounce: 200 }
29402 * ```
29403 *
29404 * Notice that the `debounce` setting now inherits the value from the outer `<div>` element.
29405 *
29406 * If you are creating a reusable component then you should be careful when using `"*": "$inherit"`
29407 * since you may inadvertently inherit a setting in the future that changes the behavior of your component.
29408 *
29409 *
29410 * ## Triggering and debouncing model updates
29411 *
29412 * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will
29413 * trigger a model update and/or a debouncing delay so that the actual update only takes place when
29414 * a timer expires; this timer will be reset after another change takes place.
Ed Tanous904063f2017-03-02 16:48:24 -080029415 *
29416 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
29417 * be different from the value in the actual model. This means that if you update the model you
Ed Tanous4758d5b2017-06-06 15:28:13 -070029418 * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in
Ed Tanous904063f2017-03-02 16:48:24 -080029419 * order to make sure it is synchronized with the model and that any debounced action is canceled.
29420 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070029421 * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue}
Ed Tanous904063f2017-03-02 16:48:24 -080029422 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
29423 * important because `form` controllers are published to the related scope under the name in their
29424 * `name` attribute.
29425 *
29426 * Any pending changes will take place immediately when an enclosing form is submitted via the
29427 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
29428 * to have access to the updated model.
29429 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070029430 * The following example shows how to override immediate updates. Changes on the inputs within the
29431 * form will update the model only when the control loses focus (blur event). If `escape` key is
29432 * pressed while the input field is focused, the value is reset to the value in the current model.
Ed Tanous904063f2017-03-02 16:48:24 -080029433 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070029434 * <example name="ngModelOptions-directive-blur" module="optionsExample">
29435 * <file name="index.html">
29436 * <div ng-controller="ExampleController">
29437 * <form name="userForm">
29438 * <label>
29439 * Name:
29440 * <input type="text" name="userName"
29441 * ng-model="user.name"
29442 * ng-model-options="{ updateOn: 'blur' }"
29443 * ng-keyup="cancel($event)" />
29444 * </label><br />
29445 * <label>
29446 * Other data:
29447 * <input type="text" ng-model="user.data" />
29448 * </label><br />
29449 * </form>
29450 * <pre>user.name = <span ng-bind="user.name"></span></pre>
29451 * </div>
29452 * </file>
29453 * <file name="app.js">
29454 * angular.module('optionsExample', [])
29455 * .controller('ExampleController', ['$scope', function($scope) {
29456 * $scope.user = { name: 'say', data: '' };
29457 *
29458 * $scope.cancel = function(e) {
29459 * if (e.keyCode === 27) {
29460 * $scope.userForm.userName.$rollbackViewValue();
29461 * }
29462 * };
29463 * }]);
29464 * </file>
29465 * <file name="protractor.js" type="protractor">
29466 * var model = element(by.binding('user.name'));
29467 * var input = element(by.model('user.name'));
29468 * var other = element(by.model('user.data'));
29469 *
29470 * it('should allow custom events', function() {
29471 * input.sendKeys(' hello');
29472 * input.click();
29473 * expect(model.getText()).toEqual('say');
29474 * other.click();
29475 * expect(model.getText()).toEqual('say hello');
29476 * });
29477 *
29478 * it('should $rollbackViewValue when model changes', function() {
29479 * input.sendKeys(' hello');
29480 * expect(input.getAttribute('value')).toEqual('say hello');
29481 * input.sendKeys(protractor.Key.ESCAPE);
29482 * expect(input.getAttribute('value')).toEqual('say');
29483 * other.click();
29484 * expect(model.getText()).toEqual('say');
29485 * });
29486 * </file>
29487 * </example>
29488 *
29489 * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
29490 * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
29491 *
29492 * <example name="ngModelOptions-directive-debounce" module="optionsExample">
29493 * <file name="index.html">
29494 * <div ng-controller="ExampleController">
29495 * <form name="userForm">
29496 * Name:
29497 * <input type="text" name="userName"
29498 * ng-model="user.name"
29499 * ng-model-options="{ debounce: 1000 }" />
29500 * <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
29501 * </form>
29502 * <pre>user.name = <span ng-bind="user.name"></span></pre>
29503 * </div>
29504 * </file>
29505 * <file name="app.js">
29506 * angular.module('optionsExample', [])
29507 * .controller('ExampleController', ['$scope', function($scope) {
29508 * $scope.user = { name: 'say' };
29509 * }]);
29510 * </file>
29511 * </example>
29512 *
29513 * ## Model updates and validation
29514 *
29515 * The default behaviour in `ngModel` is that the model value is set to `undefined` when the
29516 * validation determines that the value is invalid. By setting the `allowInvalid` property to true,
29517 * the model will still be updated even if the value is invalid.
29518 *
29519 *
29520 * ## Connecting to the scope
29521 *
29522 * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression
29523 * on the scope refers to a "getter/setter" function rather than the value itself.
29524 *
29525 * The following example shows how to bind to getter/setters:
29526 *
29527 * <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
29528 * <file name="index.html">
29529 * <div ng-controller="ExampleController">
29530 * <form name="userForm">
29531 * <label>
29532 * Name:
29533 * <input type="text" name="userName"
29534 * ng-model="user.name"
29535 * ng-model-options="{ getterSetter: true }" />
29536 * </label>
29537 * </form>
29538 * <pre>user.name = <span ng-bind="user.name()"></span></pre>
29539 * </div>
29540 * </file>
29541 * <file name="app.js">
29542 * angular.module('getterSetterExample', [])
29543 * .controller('ExampleController', ['$scope', function($scope) {
29544 * var _name = 'Brian';
29545 * $scope.user = {
29546 * name: function(newName) {
29547 * return angular.isDefined(newName) ? (_name = newName) : _name;
29548 * }
29549 * };
29550 * }]);
29551 * </file>
29552 * </example>
29553 *
29554 *
29555 * ## Specifying timezones
29556 *
29557 * You can specify the timezone that date/time input directives expect by providing its name in the
29558 * `timezone` property.
29559 *
29560 * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
29561 * and its descendents. Valid keys are:
Ed Tanous904063f2017-03-02 16:48:24 -080029562 * - `updateOn`: string specifying which event should the input be bound to. You can set several
29563 * events using an space delimited list. There is a special event called `default` that
Ed Tanous4758d5b2017-06-06 15:28:13 -070029564 * matches the default events belonging to the control.
Ed Tanous904063f2017-03-02 16:48:24 -080029565 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
29566 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
29567 * custom value for each event. For example:
Ed Tanous4758d5b2017-06-06 15:28:13 -070029568 * ```
29569 * ng-model-options="{
29570 * updateOn: 'default blur',
29571 * debounce: { 'default': 500, 'blur': 0 }
29572 * }"
29573 * ```
Ed Tanous904063f2017-03-02 16:48:24 -080029574 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
29575 * not validate correctly instead of the default behavior of setting the model to undefined.
29576 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
Ed Tanous4758d5b2017-06-06 15:28:13 -070029577 * `ngModel` as getters/setters.
Ed Tanous904063f2017-03-02 16:48:24 -080029578 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
Ed Tanous4758d5b2017-06-06 15:28:13 -070029579 * `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
Ed Tanous904063f2017-03-02 16:48:24 -080029580 * continental US time zone abbreviations, but for general use, use a time zone offset, for
29581 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
29582 * If not specified, the timezone of the browser will be used.
29583 *
Ed Tanous904063f2017-03-02 16:48:24 -080029584 */
29585var ngModelOptionsDirective = function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070029586 NgModelOptionsController.$inject = ['$attrs', '$scope'];
29587 function NgModelOptionsController($attrs, $scope) {
29588 this.$$attrs = $attrs;
29589 this.$$scope = $scope;
29590 }
29591 NgModelOptionsController.prototype = {
29592 $onInit: function() {
29593 var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions;
29594 var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions);
29595
29596 this.$options = parentOptions.createChild(modelOptionsDefinition);
29597 }
29598 };
29599
Ed Tanous904063f2017-03-02 16:48:24 -080029600 return {
29601 restrict: 'A',
Ed Tanous4758d5b2017-06-06 15:28:13 -070029602 // ngModelOptions needs to run before ngModel and input directives
29603 priority: 10,
29604 require: {parentCtrl: '?^^ngModelOptions'},
29605 bindToController: true,
29606 controller: NgModelOptionsController
Ed Tanous904063f2017-03-02 16:48:24 -080029607 };
29608};
29609
29610
Ed Tanous4758d5b2017-06-06 15:28:13 -070029611// shallow copy over values from `src` that are not already specified on `dst`
29612function defaults(dst, src) {
29613 forEach(src, function(value, key) {
29614 if (!isDefined(dst[key])) {
29615 dst[key] = value;
Ed Tanous904063f2017-03-02 16:48:24 -080029616 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070029617 });
Ed Tanous904063f2017-03-02 16:48:24 -080029618}
29619
29620/**
29621 * @ngdoc directive
29622 * @name ngNonBindable
29623 * @restrict AC
29624 * @priority 1000
29625 *
29626 * @description
29627 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
29628 * DOM element. This is useful if the element contains what appears to be Angular directives and
29629 * bindings but which should be ignored by Angular. This could be the case if you have a site that
29630 * displays snippets of code, for instance.
29631 *
29632 * @element ANY
29633 *
29634 * @example
29635 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
29636 * but the one wrapped in `ngNonBindable` is left alone.
29637 *
29638 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070029639 <example name="ng-non-bindable">
Ed Tanous904063f2017-03-02 16:48:24 -080029640 <file name="index.html">
29641 <div>Normal: {{1 + 2}}</div>
29642 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
29643 </file>
29644 <file name="protractor.js" type="protractor">
29645 it('should check ng-non-bindable', function() {
29646 expect(element(by.binding('1 + 2')).getText()).toContain('3');
29647 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
29648 });
29649 </file>
29650 </example>
29651 */
29652var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
29653
Ed Tanous4758d5b2017-06-06 15:28:13 -070029654/* exported ngOptionsDirective */
29655
Ed Tanous904063f2017-03-02 16:48:24 -080029656/* global jqLiteRemove */
29657
29658var ngOptionsMinErr = minErr('ngOptions');
29659
29660/**
29661 * @ngdoc directive
29662 * @name ngOptions
29663 * @restrict A
29664 *
29665 * @description
29666 *
29667 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
29668 * elements for the `<select>` element using the array or object obtained by evaluating the
29669 * `ngOptions` comprehension expression.
29670 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070029671 * In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements instead of
29672 * `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits:
29673 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
29674 * comprehension expression
29675 * - reduced memory consumption by not creating a new scope for each repeated instance
29676 * - increased render speed by creating the options in a documentFragment instead of individually
Ed Tanous904063f2017-03-02 16:48:24 -080029677 *
29678 * When an item in the `<select>` menu is selected, the array element or object property
29679 * represented by the selected option will be bound to the model identified by the `ngModel`
29680 * directive.
29681 *
29682 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
29683 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
29684 * option. See example below for demonstration.
29685 *
29686 * ## Complex Models (objects or collections)
29687 *
29688 * By default, `ngModel` watches the model by reference, not value. This is important to know when
29689 * binding the select to a model that is an object or a collection.
29690 *
29691 * One issue occurs if you want to preselect an option. For example, if you set
29692 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
29693 * because the objects are not identical. So by default, you should always reference the item in your collection
29694 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
29695 *
29696 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
29697 * of the item not by reference, but by the result of the `track by` expression. For example, if your
29698 * collection items have an id property, you would `track by item.id`.
29699 *
29700 * A different issue with objects or collections is that ngModel won't detect if an object property or
29701 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
29702 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
29703 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
29704 * has not changed identity, but only a property on the object or an item in the collection changes.
29705 *
29706 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
29707 * if the model is an array). This means that changing a property deeper than the first level inside the
29708 * object/collection will not trigger a re-rendering.
29709 *
29710 * ## `select` **`as`**
29711 *
29712 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
29713 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
29714 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
29715 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
29716 *
29717 *
29718 * ### `select` **`as`** and **`track by`**
29719 *
29720 * <div class="alert alert-warning">
29721 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
29722 * </div>
29723 *
29724 * Given this array of items on the $scope:
29725 *
29726 * ```js
29727 * $scope.items = [{
29728 * id: 1,
29729 * label: 'aLabel',
29730 * subItem: { name: 'aSubItem' }
29731 * }, {
29732 * id: 2,
29733 * label: 'bLabel',
29734 * subItem: { name: 'bSubItem' }
29735 * }];
29736 * ```
29737 *
29738 * This will work:
29739 *
29740 * ```html
29741 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
29742 * ```
29743 * ```js
29744 * $scope.selected = $scope.items[0];
29745 * ```
29746 *
29747 * but this will not work:
29748 *
29749 * ```html
29750 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
29751 * ```
29752 * ```js
29753 * $scope.selected = $scope.items[0].subItem;
29754 * ```
29755 *
29756 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
29757 * `items` array. Because the selected option has been set programmatically in the controller, the
29758 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
29759 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
29760 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
29761 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
29762 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
29763 *
29764 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070029765 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
29766 * @param {comprehension_expression} ngOptions in one of the following forms:
Ed Tanous904063f2017-03-02 16:48:24 -080029767 *
29768 * * for array data sources:
29769 * * `label` **`for`** `value` **`in`** `array`
29770 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
29771 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
29772 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
29773 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
29774 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
29775 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
29776 * (for including a filter with `track by`)
29777 * * for object data sources:
29778 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
29779 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
29780 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
29781 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
29782 * * `select` **`as`** `label` **`group by`** `group`
29783 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
29784 * * `select` **`as`** `label` **`disable when`** `disable`
29785 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
29786 *
29787 * Where:
29788 *
29789 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
29790 * * `value`: local variable which will refer to each item in the `array` or each property value
29791 * of `object` during iteration.
29792 * * `key`: local variable which will refer to a property name in `object` during iteration.
29793 * * `label`: The result of this expression will be the label for `<option>` element. The
29794 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
29795 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
29796 * element. If not specified, `select` expression will default to `value`.
29797 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
29798 * DOM element.
29799 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
29800 * element. Return `true` to disable.
29801 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
29802 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
29803 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
29804 * even when the options are recreated (e.g. reloaded from the server).
Ed Tanous4758d5b2017-06-06 15:28:13 -070029805 * @param {string=} name Property name of the form under which the control is published.
29806 * @param {string=} required The control is considered valid only if value is entered.
29807 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
29808 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
29809 * `required` when you want to data-bind to the `required` attribute.
29810 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
29811 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
Ed Tanous904063f2017-03-02 16:48:24 -080029812 *
29813 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070029814 <example module="selectExample" name="select">
Ed Tanous904063f2017-03-02 16:48:24 -080029815 <file name="index.html">
29816 <script>
29817 angular.module('selectExample', [])
29818 .controller('ExampleController', ['$scope', function($scope) {
29819 $scope.colors = [
29820 {name:'black', shade:'dark'},
29821 {name:'white', shade:'light', notAnOption: true},
29822 {name:'red', shade:'dark'},
29823 {name:'blue', shade:'dark', notAnOption: true},
29824 {name:'yellow', shade:'light', notAnOption: false}
29825 ];
29826 $scope.myColor = $scope.colors[2]; // red
29827 }]);
29828 </script>
29829 <div ng-controller="ExampleController">
29830 <ul>
29831 <li ng-repeat="color in colors">
29832 <label>Name: <input ng-model="color.name"></label>
29833 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
29834 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
29835 </li>
29836 <li>
29837 <button ng-click="colors.push({})">add</button>
29838 </li>
29839 </ul>
29840 <hr/>
29841 <label>Color (null not allowed):
29842 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
29843 </label><br/>
29844 <label>Color (null allowed):
29845 <span class="nullable">
29846 <select ng-model="myColor" ng-options="color.name for color in colors">
29847 <option value="">-- choose color --</option>
29848 </select>
29849 </span></label><br/>
29850
29851 <label>Color grouped by shade:
29852 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
29853 </select>
29854 </label><br/>
29855
29856 <label>Color grouped by shade, with some disabled:
29857 <select ng-model="myColor"
29858 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
29859 </select>
29860 </label><br/>
29861
29862
29863
29864 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
29865 <br/>
29866 <hr/>
29867 Currently selected: {{ {selected_color:myColor} }}
29868 <div style="border:solid 1px black; height:20px"
29869 ng-style="{'background-color':myColor.name}">
29870 </div>
29871 </div>
29872 </file>
29873 <file name="protractor.js" type="protractor">
29874 it('should check ng-options', function() {
29875 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
29876 element.all(by.model('myColor')).first().click();
29877 element.all(by.css('select[ng-model="myColor"] option')).first().click();
29878 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
29879 element(by.css('.nullable select[ng-model="myColor"]')).click();
29880 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
29881 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
29882 });
29883 </file>
29884 </example>
29885 */
29886
Ed Tanous4758d5b2017-06-06 15:28:13 -070029887/* eslint-disable max-len */
29888// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555000000000666666666666600000007777777777777000000000000000888888888800000000000000000009999999999
29889var 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]+?))?$/;
Ed Tanous904063f2017-03-02 16:48:24 -080029890 // 1: value expression (valueFn)
29891 // 2: label expression (displayFn)
29892 // 3: group by expression (groupByFn)
29893 // 4: disable when expression (disableWhenFn)
29894 // 5: array item variable name
29895 // 6: object item key variable name
29896 // 7: object item value variable name
29897 // 8: collection expression
29898 // 9: track by expression
Ed Tanous4758d5b2017-06-06 15:28:13 -070029899/* eslint-enable */
Ed Tanous904063f2017-03-02 16:48:24 -080029900
29901
29902var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) {
29903
29904 function parseOptionsExpression(optionsExp, selectElement, scope) {
29905
29906 var match = optionsExp.match(NG_OPTIONS_REGEXP);
29907 if (!(match)) {
29908 throw ngOptionsMinErr('iexp',
Ed Tanous4758d5b2017-06-06 15:28:13 -070029909 'Expected expression in form of ' +
29910 '\'_select_ (as _label_)? for (_key_,)?_value_ in _collection_\'' +
29911 ' but got \'{0}\'. Element: {1}',
Ed Tanous904063f2017-03-02 16:48:24 -080029912 optionsExp, startingTag(selectElement));
29913 }
29914
29915 // Extract the parts from the ngOptions expression
29916
29917 // The variable name for the value of the item in the collection
29918 var valueName = match[5] || match[7];
29919 // The variable name for the key of the item in the collection
29920 var keyName = match[6];
29921
29922 // An expression that generates the viewValue for an option if there is a label expression
29923 var selectAs = / as /.test(match[0]) && match[1];
29924 // An expression that is used to track the id of each object in the options collection
29925 var trackBy = match[9];
29926 // An expression that generates the viewValue for an option if there is no label expression
29927 var valueFn = $parse(match[2] ? match[1] : valueName);
29928 var selectAsFn = selectAs && $parse(selectAs);
29929 var viewValueFn = selectAsFn || valueFn;
29930 var trackByFn = trackBy && $parse(trackBy);
29931
29932 // Get the value by which we are going to track the option
29933 // if we have a trackFn then use that (passing scope and locals)
29934 // otherwise just hash the given viewValue
29935 var getTrackByValueFn = trackBy ?
29936 function(value, locals) { return trackByFn(scope, locals); } :
29937 function getHashOfValue(value) { return hashKey(value); };
29938 var getTrackByValue = function(value, key) {
29939 return getTrackByValueFn(value, getLocals(value, key));
29940 };
29941
29942 var displayFn = $parse(match[2] || match[1]);
29943 var groupByFn = $parse(match[3] || '');
29944 var disableWhenFn = $parse(match[4] || '');
29945 var valuesFn = $parse(match[8]);
29946
29947 var locals = {};
29948 var getLocals = keyName ? function(value, key) {
29949 locals[keyName] = key;
29950 locals[valueName] = value;
29951 return locals;
29952 } : function(value) {
29953 locals[valueName] = value;
29954 return locals;
29955 };
29956
29957
29958 function Option(selectValue, viewValue, label, group, disabled) {
29959 this.selectValue = selectValue;
29960 this.viewValue = viewValue;
29961 this.label = label;
29962 this.group = group;
29963 this.disabled = disabled;
29964 }
29965
29966 function getOptionValuesKeys(optionValues) {
29967 var optionValuesKeys;
29968
29969 if (!keyName && isArrayLike(optionValues)) {
29970 optionValuesKeys = optionValues;
29971 } else {
29972 // if object, extract keys, in enumeration order, unsorted
29973 optionValuesKeys = [];
29974 for (var itemKey in optionValues) {
29975 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
29976 optionValuesKeys.push(itemKey);
29977 }
29978 }
29979 }
29980 return optionValuesKeys;
29981 }
29982
29983 return {
29984 trackBy: trackBy,
29985 getTrackByValue: getTrackByValue,
29986 getWatchables: $parse(valuesFn, function(optionValues) {
29987 // Create a collection of things that we would like to watch (watchedArray)
29988 // so that they can all be watched using a single $watchCollection
29989 // that only runs the handler once if anything changes
29990 var watchedArray = [];
29991 optionValues = optionValues || [];
29992
29993 var optionValuesKeys = getOptionValuesKeys(optionValues);
29994 var optionValuesLength = optionValuesKeys.length;
29995 for (var index = 0; index < optionValuesLength; index++) {
29996 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
29997 var value = optionValues[key];
29998
29999 var locals = getLocals(value, key);
30000 var selectValue = getTrackByValueFn(value, locals);
30001 watchedArray.push(selectValue);
30002
30003 // Only need to watch the displayFn if there is a specific label expression
30004 if (match[2] || match[1]) {
30005 var label = displayFn(scope, locals);
30006 watchedArray.push(label);
30007 }
30008
30009 // Only need to watch the disableWhenFn if there is a specific disable expression
30010 if (match[4]) {
30011 var disableWhen = disableWhenFn(scope, locals);
30012 watchedArray.push(disableWhen);
30013 }
30014 }
30015 return watchedArray;
30016 }),
30017
30018 getOptions: function() {
30019
30020 var optionItems = [];
30021 var selectValueMap = {};
30022
30023 // The option values were already computed in the `getWatchables` fn,
30024 // which must have been called to trigger `getOptions`
30025 var optionValues = valuesFn(scope) || [];
30026 var optionValuesKeys = getOptionValuesKeys(optionValues);
30027 var optionValuesLength = optionValuesKeys.length;
30028
30029 for (var index = 0; index < optionValuesLength; index++) {
30030 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
30031 var value = optionValues[key];
30032 var locals = getLocals(value, key);
30033 var viewValue = viewValueFn(scope, locals);
30034 var selectValue = getTrackByValueFn(viewValue, locals);
30035 var label = displayFn(scope, locals);
30036 var group = groupByFn(scope, locals);
30037 var disabled = disableWhenFn(scope, locals);
30038 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
30039
30040 optionItems.push(optionItem);
30041 selectValueMap[selectValue] = optionItem;
30042 }
30043
30044 return {
30045 items: optionItems,
30046 selectValueMap: selectValueMap,
30047 getOptionFromViewValue: function(value) {
30048 return selectValueMap[getTrackByValue(value)];
30049 },
30050 getViewValueFromOption: function(option) {
30051 // If the viewValue could be an object that may be mutated by the application,
30052 // we need to make a copy and not return the reference to the value on the option.
Ed Tanous4758d5b2017-06-06 15:28:13 -070030053 return trackBy ? copy(option.viewValue) : option.viewValue;
Ed Tanous904063f2017-03-02 16:48:24 -080030054 }
30055 };
30056 }
30057 };
30058 }
30059
30060
30061 // we can't just jqLite('<option>') since jqLite is not smart enough
30062 // to create it in <select> and IE barfs otherwise.
30063 var optionTemplate = window.document.createElement('option'),
30064 optGroupTemplate = window.document.createElement('optgroup');
30065
30066 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
30067
30068 var selectCtrl = ctrls[0];
30069 var ngModelCtrl = ctrls[1];
30070 var multiple = attr.multiple;
30071
30072 // The emptyOption allows the application developer to provide their own custom "empty"
30073 // option when the viewValue does not match any of the option values.
Ed Tanous904063f2017-03-02 16:48:24 -080030074 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
30075 if (children[i].value === '') {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030076 selectCtrl.hasEmptyOption = true;
30077 selectCtrl.emptyOption = children.eq(i);
Ed Tanous904063f2017-03-02 16:48:24 -080030078 break;
30079 }
30080 }
30081
Ed Tanous4758d5b2017-06-06 15:28:13 -070030082 var providedEmptyOption = !!selectCtrl.emptyOption;
Ed Tanous904063f2017-03-02 16:48:24 -080030083
30084 var unknownOption = jqLite(optionTemplate.cloneNode(false));
30085 unknownOption.val('?');
30086
30087 var options;
30088 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
30089 // This stores the newly created options before they are appended to the select.
30090 // Since the contents are removed from the fragment when it is appended,
30091 // we only need to create it once.
30092 var listFragment = $document[0].createDocumentFragment();
30093
Ed Tanous4758d5b2017-06-06 15:28:13 -070030094 // Overwrite the implementation. ngOptions doesn't use hashes
30095 selectCtrl.generateUnknownOptionValue = function(val) {
30096 return '?';
Ed Tanous904063f2017-03-02 16:48:24 -080030097 };
30098
30099 // Update the controller methods for multiple selectable options
30100 if (!multiple) {
30101
30102 selectCtrl.writeValue = function writeNgOptionsValue(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030103 var selectedOption = options.selectValueMap[selectElement.val()];
Ed Tanous904063f2017-03-02 16:48:24 -080030104 var option = options.getOptionFromViewValue(value);
30105
Ed Tanous4758d5b2017-06-06 15:28:13 -070030106 // Make sure to remove the selected attribute from the previously selected option
30107 // Otherwise, screen readers might get confused
30108 if (selectedOption) selectedOption.element.removeAttribute('selected');
30109
Ed Tanous904063f2017-03-02 16:48:24 -080030110 if (option) {
30111 // Don't update the option when it is already selected.
30112 // For example, the browser will select the first option by default. In that case,
30113 // most properties are set automatically - except the `selected` attribute, which we
30114 // set always
30115
30116 if (selectElement[0].value !== option.selectValue) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030117 selectCtrl.removeUnknownOption();
30118 selectCtrl.unselectEmptyOption();
Ed Tanous904063f2017-03-02 16:48:24 -080030119
30120 selectElement[0].value = option.selectValue;
30121 option.element.selected = true;
30122 }
30123
30124 option.element.setAttribute('selected', 'selected');
30125 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030126
30127 if (providedEmptyOption) {
30128 selectCtrl.selectEmptyOption();
30129 } else if (selectCtrl.unknownOption.parent().length) {
30130 selectCtrl.updateUnknownOption(value);
Ed Tanous904063f2017-03-02 16:48:24 -080030131 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030132 selectCtrl.renderUnknownOption(value);
Ed Tanous904063f2017-03-02 16:48:24 -080030133 }
30134 }
30135 };
30136
30137 selectCtrl.readValue = function readNgOptionsValue() {
30138
30139 var selectedOption = options.selectValueMap[selectElement.val()];
30140
30141 if (selectedOption && !selectedOption.disabled) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030142 selectCtrl.unselectEmptyOption();
30143 selectCtrl.removeUnknownOption();
Ed Tanous904063f2017-03-02 16:48:24 -080030144 return options.getViewValueFromOption(selectedOption);
30145 }
30146 return null;
30147 };
30148
30149 // If we are using `track by` then we must watch the tracked value on the model
30150 // since ngModel only watches for object identity change
Ed Tanous4758d5b2017-06-06 15:28:13 -070030151 // FIXME: When a user selects an option, this watch will fire needlessly
Ed Tanous904063f2017-03-02 16:48:24 -080030152 if (ngOptions.trackBy) {
30153 scope.$watch(
30154 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
30155 function() { ngModelCtrl.$render(); }
30156 );
30157 }
30158
30159 } else {
30160
Ed Tanous4758d5b2017-06-06 15:28:13 -070030161 selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
30162 // Only set `<option>.selected` if necessary, in order to prevent some browsers from
30163 // scrolling to `<option>` elements that are outside the `<select>` element's viewport.
Ed Tanous904063f2017-03-02 16:48:24 -080030164
Ed Tanous4758d5b2017-06-06 15:28:13 -070030165 var selectedOptions = values && values.map(getAndUpdateSelectedOption) || [];
Ed Tanous904063f2017-03-02 16:48:24 -080030166
Ed Tanous904063f2017-03-02 16:48:24 -080030167 options.items.forEach(function(option) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030168 if (option.element.selected && !includes(selectedOptions, option)) {
30169 option.element.selected = false;
30170 }
Ed Tanous904063f2017-03-02 16:48:24 -080030171 });
Ed Tanous904063f2017-03-02 16:48:24 -080030172 };
30173
30174
30175 selectCtrl.readValue = function readNgOptionsMultiple() {
30176 var selectedValues = selectElement.val() || [],
30177 selections = [];
30178
30179 forEach(selectedValues, function(value) {
30180 var option = options.selectValueMap[value];
30181 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
30182 });
30183
30184 return selections;
30185 };
30186
30187 // If we are using `track by` then we must watch these tracked values on the model
30188 // since ngModel only watches for object identity change
30189 if (ngOptions.trackBy) {
30190
30191 scope.$watchCollection(function() {
30192 if (isArray(ngModelCtrl.$viewValue)) {
30193 return ngModelCtrl.$viewValue.map(function(value) {
30194 return ngOptions.getTrackByValue(value);
30195 });
30196 }
30197 }, function() {
30198 ngModelCtrl.$render();
30199 });
30200
30201 }
30202 }
30203
Ed Tanous904063f2017-03-02 16:48:24 -080030204 if (providedEmptyOption) {
30205
30206 // we need to remove it before calling selectElement.empty() because otherwise IE will
30207 // remove the label from the element. wtf?
Ed Tanous4758d5b2017-06-06 15:28:13 -070030208 selectCtrl.emptyOption.remove();
Ed Tanous904063f2017-03-02 16:48:24 -080030209
30210 // compile the element since there might be bindings in it
Ed Tanous4758d5b2017-06-06 15:28:13 -070030211 $compile(selectCtrl.emptyOption)(scope);
Ed Tanous904063f2017-03-02 16:48:24 -080030212
Ed Tanous4758d5b2017-06-06 15:28:13 -070030213 if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) {
30214 // This means the empty option has currently no actual DOM node, probably because
30215 // it has been modified by a transclusion directive.
30216 selectCtrl.hasEmptyOption = false;
30217
30218 // Redefine the registerOption function, which will catch
30219 // options that are added by ngIf etc. (rendering of the node is async because of
30220 // lazy transclusion)
30221 selectCtrl.registerOption = function(optionScope, optionEl) {
30222 if (optionEl.val() === '') {
30223 selectCtrl.hasEmptyOption = true;
30224 selectCtrl.emptyOption = optionEl;
30225 selectCtrl.emptyOption.removeClass('ng-scope');
30226 // This ensures the new empty option is selected if previously no option was selected
30227 ngModelCtrl.$render();
30228
30229 optionEl.on('$destroy', function() {
30230 selectCtrl.hasEmptyOption = false;
30231 selectCtrl.emptyOption = undefined;
30232 });
30233 }
30234 };
30235
30236 } else {
30237 // remove the class, which is added automatically because we recompile the element and it
30238 // becomes the compilation root
30239 selectCtrl.emptyOption.removeClass('ng-scope');
30240 }
30241
Ed Tanous904063f2017-03-02 16:48:24 -080030242 }
30243
30244 selectElement.empty();
30245
30246 // We need to do this here to ensure that the options object is defined
30247 // when we first hit it in writeNgOptionsValue
30248 updateOptions();
30249
30250 // We will re-render the option elements if the option values or labels change
30251 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
30252
30253 // ------------------------------------------------------------------ //
30254
30255 function addOptionElement(option, parent) {
30256 var optionElement = optionTemplate.cloneNode(false);
30257 parent.appendChild(optionElement);
30258 updateOptionElement(option, optionElement);
30259 }
30260
Ed Tanous4758d5b2017-06-06 15:28:13 -070030261 function getAndUpdateSelectedOption(viewValue) {
30262 var option = options.getOptionFromViewValue(viewValue);
30263 var element = option && option.element;
30264
30265 if (element && !element.selected) element.selected = true;
30266
30267 return option;
30268 }
Ed Tanous904063f2017-03-02 16:48:24 -080030269
30270 function updateOptionElement(option, element) {
30271 option.element = element;
30272 element.disabled = option.disabled;
30273 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
30274 // selects in certain circumstances when multiple selects are next to each other and display
30275 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
30276 // See https://github.com/angular/angular.js/issues/11314 for more info.
30277 // This is unfortunately untestable with unit / e2e tests
30278 if (option.label !== element.label) {
30279 element.label = option.label;
30280 element.textContent = option.label;
30281 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070030282 element.value = option.selectValue;
Ed Tanous904063f2017-03-02 16:48:24 -080030283 }
30284
30285 function updateOptions() {
30286 var previousValue = options && selectCtrl.readValue();
30287
30288 // We must remove all current options, but cannot simply set innerHTML = null
30289 // since the providedEmptyOption might have an ngIf on it that inserts comments which we
30290 // must preserve.
30291 // Instead, iterate over the current option elements and remove them or their optgroup
30292 // parents
30293 if (options) {
30294
30295 for (var i = options.items.length - 1; i >= 0; i--) {
30296 var option = options.items[i];
30297 if (isDefined(option.group)) {
30298 jqLiteRemove(option.element.parentNode);
30299 } else {
30300 jqLiteRemove(option.element);
30301 }
30302 }
30303 }
30304
30305 options = ngOptions.getOptions();
30306
30307 var groupElementMap = {};
30308
30309 // Ensure that the empty option is always there if it was explicitly provided
30310 if (providedEmptyOption) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030311 selectElement.prepend(selectCtrl.emptyOption);
Ed Tanous904063f2017-03-02 16:48:24 -080030312 }
30313
30314 options.items.forEach(function addOption(option) {
30315 var groupElement;
30316
30317 if (isDefined(option.group)) {
30318
30319 // This option is to live in a group
30320 // See if we have already created this group
30321 groupElement = groupElementMap[option.group];
30322
30323 if (!groupElement) {
30324
30325 groupElement = optGroupTemplate.cloneNode(false);
30326 listFragment.appendChild(groupElement);
30327
30328 // Update the label on the group element
30329 // "null" is special cased because of Safari
30330 groupElement.label = option.group === null ? 'null' : option.group;
30331
30332 // Store it for use later
30333 groupElementMap[option.group] = groupElement;
30334 }
30335
30336 addOptionElement(option, groupElement);
30337
30338 } else {
30339
30340 // This option is not in a group
30341 addOptionElement(option, listFragment);
30342 }
30343 });
30344
30345 selectElement[0].appendChild(listFragment);
30346
30347 ngModelCtrl.$render();
30348
30349 // Check to see if the value has changed due to the update to the options
30350 if (!ngModelCtrl.$isEmpty(previousValue)) {
30351 var nextValue = selectCtrl.readValue();
30352 var isNotPrimitive = ngOptions.trackBy || multiple;
30353 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
30354 ngModelCtrl.$setViewValue(nextValue);
30355 ngModelCtrl.$render();
30356 }
30357 }
30358
30359 }
30360 }
30361
30362 return {
30363 restrict: 'A',
30364 terminal: true,
30365 require: ['select', 'ngModel'],
30366 link: {
30367 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
30368 // Deactivate the SelectController.register method to prevent
30369 // option directives from accidentally registering themselves
30370 // (and unwanted $destroy handlers etc.)
30371 ctrls[0].registerOption = noop;
30372 },
30373 post: ngOptionsPostLink
30374 }
30375 };
30376}];
30377
30378/**
30379 * @ngdoc directive
30380 * @name ngPluralize
30381 * @restrict EA
30382 *
30383 * @description
30384 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
30385 * These rules are bundled with angular.js, but can be overridden
30386 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
30387 * by specifying the mappings between
30388 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
30389 * and the strings to be displayed.
30390 *
30391 * # Plural categories and explicit number rules
30392 * There are two
30393 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
30394 * in Angular's default en-US locale: "one" and "other".
30395 *
30396 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
30397 * any number that is not 1), an explicit number rule can only match one number. For example, the
30398 * explicit number rule for "3" matches the number 3. There are examples of plural categories
30399 * and explicit number rules throughout the rest of this documentation.
30400 *
30401 * # Configuring ngPluralize
30402 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
30403 * You can also provide an optional attribute, `offset`.
30404 *
30405 * The value of the `count` attribute can be either a string or an {@link guide/expression
30406 * Angular expression}; these are evaluated on the current scope for its bound value.
30407 *
30408 * The `when` attribute specifies the mappings between plural categories and the actual
30409 * string to be displayed. The value of the attribute should be a JSON object.
30410 *
30411 * The following example shows how to configure ngPluralize:
30412 *
30413 * ```html
30414 * <ng-pluralize count="personCount"
30415 when="{'0': 'Nobody is viewing.',
30416 * 'one': '1 person is viewing.',
30417 * 'other': '{} people are viewing.'}">
30418 * </ng-pluralize>
30419 *```
30420 *
30421 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
30422 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
30423 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
30424 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
30425 * show "a dozen people are viewing".
30426 *
30427 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
30428 * into pluralized strings. In the previous example, Angular will replace `{}` with
30429 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
30430 * for <span ng-non-bindable>{{numberExpression}}</span>.
30431 *
30432 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
30433 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
30434 *
30435 * # Configuring ngPluralize with offset
30436 * The `offset` attribute allows further customization of pluralized text, which can result in
30437 * a better user experience. For example, instead of the message "4 people are viewing this document",
30438 * you might display "John, Kate and 2 others are viewing this document".
30439 * The offset attribute allows you to offset a number by any desired value.
30440 * Let's take a look at an example:
30441 *
30442 * ```html
30443 * <ng-pluralize count="personCount" offset=2
30444 * when="{'0': 'Nobody is viewing.',
30445 * '1': '{{person1}} is viewing.',
30446 * '2': '{{person1}} and {{person2}} are viewing.',
30447 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
30448 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
30449 * </ng-pluralize>
30450 * ```
30451 *
30452 * Notice that we are still using two plural categories(one, other), but we added
30453 * three explicit number rules 0, 1 and 2.
30454 * When one person, perhaps John, views the document, "John is viewing" will be shown.
30455 * When three people view the document, no explicit number rule is found, so
30456 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
30457 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
30458 * is shown.
30459 *
30460 * Note that when you specify offsets, you must provide explicit number rules for
30461 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
30462 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
30463 * plural categories "one" and "other".
30464 *
30465 * @param {string|expression} count The variable to be bound to.
30466 * @param {string} when The mapping between plural category to its corresponding strings.
30467 * @param {number=} offset Offset to deduct from the total number.
30468 *
30469 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070030470 <example module="pluralizeExample" name="ng-pluralize">
Ed Tanous904063f2017-03-02 16:48:24 -080030471 <file name="index.html">
30472 <script>
30473 angular.module('pluralizeExample', [])
30474 .controller('ExampleController', ['$scope', function($scope) {
30475 $scope.person1 = 'Igor';
30476 $scope.person2 = 'Misko';
30477 $scope.personCount = 1;
30478 }]);
30479 </script>
30480 <div ng-controller="ExampleController">
30481 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
30482 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
30483 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
30484
30485 <!--- Example with simple pluralization rules for en locale --->
30486 Without Offset:
30487 <ng-pluralize count="personCount"
30488 when="{'0': 'Nobody is viewing.',
30489 'one': '1 person is viewing.',
30490 'other': '{} people are viewing.'}">
30491 </ng-pluralize><br>
30492
30493 <!--- Example with offset --->
30494 With Offset(2):
30495 <ng-pluralize count="personCount" offset=2
30496 when="{'0': 'Nobody is viewing.',
30497 '1': '{{person1}} is viewing.',
30498 '2': '{{person1}} and {{person2}} are viewing.',
30499 'one': '{{person1}}, {{person2}} and one other person are viewing.',
30500 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
30501 </ng-pluralize>
30502 </div>
30503 </file>
30504 <file name="protractor.js" type="protractor">
30505 it('should show correct pluralized string', function() {
30506 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
30507 var withOffset = element.all(by.css('ng-pluralize')).get(1);
30508 var countInput = element(by.model('personCount'));
30509
30510 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
30511 expect(withOffset.getText()).toEqual('Igor is viewing.');
30512
30513 countInput.clear();
30514 countInput.sendKeys('0');
30515
30516 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
30517 expect(withOffset.getText()).toEqual('Nobody is viewing.');
30518
30519 countInput.clear();
30520 countInput.sendKeys('2');
30521
30522 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
30523 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
30524
30525 countInput.clear();
30526 countInput.sendKeys('3');
30527
30528 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
30529 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
30530
30531 countInput.clear();
30532 countInput.sendKeys('4');
30533
30534 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
30535 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
30536 });
30537 it('should show data-bound names', function() {
30538 var withOffset = element.all(by.css('ng-pluralize')).get(1);
30539 var personCount = element(by.model('personCount'));
30540 var person1 = element(by.model('person1'));
30541 var person2 = element(by.model('person2'));
30542 personCount.clear();
30543 personCount.sendKeys('4');
30544 person1.clear();
30545 person1.sendKeys('Di');
30546 person2.clear();
30547 person2.sendKeys('Vojta');
30548 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
30549 });
30550 </file>
30551 </example>
30552 */
30553var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
30554 var BRACE = /{}/g,
30555 IS_WHEN = /^when(Minus)?(.+)$/;
30556
30557 return {
30558 link: function(scope, element, attr) {
30559 var numberExp = attr.count,
30560 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
30561 offset = attr.offset || 0,
30562 whens = scope.$eval(whenExp) || {},
30563 whensExpFns = {},
30564 startSymbol = $interpolate.startSymbol(),
30565 endSymbol = $interpolate.endSymbol(),
30566 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
30567 watchRemover = angular.noop,
30568 lastCount;
30569
30570 forEach(attr, function(expression, attributeName) {
30571 var tmpMatch = IS_WHEN.exec(attributeName);
30572 if (tmpMatch) {
30573 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
30574 whens[whenKey] = element.attr(attr.$attr[attributeName]);
30575 }
30576 });
30577 forEach(whens, function(expression, key) {
30578 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
30579
30580 });
30581
30582 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
30583 var count = parseFloat(newVal);
Ed Tanous4758d5b2017-06-06 15:28:13 -070030584 var countIsNaN = isNumberNaN(count);
Ed Tanous904063f2017-03-02 16:48:24 -080030585
30586 if (!countIsNaN && !(count in whens)) {
30587 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
30588 // Otherwise, check it against pluralization rules in $locale service.
30589 count = $locale.pluralCat(count - offset);
30590 }
30591
30592 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
30593 // In JS `NaN !== NaN`, so we have to explicitly check.
Ed Tanous4758d5b2017-06-06 15:28:13 -070030594 if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) {
Ed Tanous904063f2017-03-02 16:48:24 -080030595 watchRemover();
30596 var whenExpFn = whensExpFns[count];
30597 if (isUndefined(whenExpFn)) {
30598 if (newVal != null) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030599 $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp);
Ed Tanous904063f2017-03-02 16:48:24 -080030600 }
30601 watchRemover = noop;
30602 updateElementText();
30603 } else {
30604 watchRemover = scope.$watch(whenExpFn, updateElementText);
30605 }
30606 lastCount = count;
30607 }
30608 });
30609
30610 function updateElementText(newText) {
30611 element.text(newText || '');
30612 }
30613 }
30614 };
30615}];
30616
Ed Tanous4758d5b2017-06-06 15:28:13 -070030617/* exported ngRepeatDirective */
30618
Ed Tanous904063f2017-03-02 16:48:24 -080030619/**
30620 * @ngdoc directive
30621 * @name ngRepeat
30622 * @multiElement
Ed Tanous4758d5b2017-06-06 15:28:13 -070030623 * @restrict A
Ed Tanous904063f2017-03-02 16:48:24 -080030624 *
30625 * @description
30626 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
30627 * instance gets its own scope, where the given loop variable is set to the current collection item,
30628 * and `$index` is set to the item index or key.
30629 *
30630 * Special properties are exposed on the local scope of each template instance, including:
30631 *
30632 * | Variable | Type | Details |
30633 * |-----------|-----------------|-----------------------------------------------------------------------------|
30634 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
30635 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
30636 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
30637 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
30638 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
30639 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
30640 *
30641 * <div class="alert alert-info">
30642 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
30643 * This may be useful when, for instance, nesting ngRepeats.
30644 * </div>
30645 *
30646 *
30647 * # Iterating over object properties
30648 *
30649 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
30650 * syntax:
30651 *
30652 * ```js
30653 * <div ng-repeat="(key, value) in myObj"> ... </div>
30654 * ```
30655 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070030656 * However, there are a few limitations compared to array iteration:
Ed Tanous904063f2017-03-02 16:48:24 -080030657 *
30658 * - The JavaScript specification does not define the order of keys
30659 * returned for an object, so Angular relies on the order returned by the browser
30660 * when running `for key in myObj`. Browsers generally follow the strategy of providing
30661 * keys in the order in which they were defined, although there are exceptions when keys are deleted
30662 * and reinstated. See the
30663 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
30664 *
30665 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because
30666 * it's a prefix used by Angular for public (`$`) and private (`$$`) properties.
30667 *
30668 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
30669 * objects, and will throw an error if used with one.
30670 *
30671 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array
30672 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
30673 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
30674 * or implement a `$watch` on the object yourself.
30675 *
30676 *
30677 * # Tracking and Duplicates
30678 *
30679 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
Ed Tanous4758d5b2017-06-06 15:28:13 -070030680 * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM:
Ed Tanous904063f2017-03-02 16:48:24 -080030681 *
30682 * * When an item is added, a new instance of the template is added to the DOM.
30683 * * When an item is removed, its template instance is removed from the DOM.
30684 * * When items are reordered, their respective templates are reordered in the DOM.
30685 *
30686 * To minimize creation of DOM elements, `ngRepeat` uses a function
30687 * to "keep track" of all items in the collection and their corresponding DOM elements.
Ed Tanous4758d5b2017-06-06 15:28:13 -070030688 * For example, if an item is added to the collection, `ngRepeat` will know that all other items
Ed Tanous904063f2017-03-02 16:48:24 -080030689 * already have DOM elements, and will not re-render them.
30690 *
30691 * The default tracking function (which tracks items by their identity) does not allow
30692 * duplicate items in arrays. This is because when there are duplicates, it is not possible
30693 * to maintain a one-to-one mapping between collection items and DOM elements.
30694 *
30695 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
30696 * with your own using the `track by` expression.
30697 *
30698 * For example, you may track items by the index of each item in the collection, using the
30699 * special scope property `$index`:
30700 * ```html
30701 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
30702 * {{n}}
30703 * </div>
30704 * ```
30705 *
30706 * You may also use arbitrary expressions in `track by`, including references to custom functions
30707 * on the scope:
30708 * ```html
30709 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
30710 * {{n}}
30711 * </div>
30712 * ```
30713 *
30714 * <div class="alert alert-success">
Ed Tanous4758d5b2017-06-06 15:28:13 -070030715 * If you are working with objects that have a unique identifier property, you should track
30716 * by this identifier instead of the object instance. Should you reload your data later, `ngRepeat`
Ed Tanous904063f2017-03-02 16:48:24 -080030717 * will not have to rebuild the DOM elements for items it has already rendered, even if the
30718 * JavaScript objects in the collection have been substituted for new ones. For large collections,
30719 * this significantly improves rendering performance. If you don't have a unique identifier,
30720 * `track by $index` can also provide a performance boost.
30721 * </div>
Ed Tanous4758d5b2017-06-06 15:28:13 -070030722 *
Ed Tanous904063f2017-03-02 16:48:24 -080030723 * ```html
30724 * <div ng-repeat="model in collection track by model.id">
30725 * {{model.name}}
30726 * </div>
30727 * ```
30728 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070030729 * <br />
30730 * <div class="alert alert-warning">
30731 * Avoid using `track by $index` when the repeated template contains
30732 * {@link guide/expression#one-time-binding one-time bindings}. In such cases, the `nth` DOM
30733 * element will always be matched with the `nth` item of the array, so the bindings on that element
30734 * will not be updated even when the corresponding item changes, essentially causing the view to get
30735 * out-of-sync with the underlying data.
30736 * </div>
30737 *
Ed Tanous904063f2017-03-02 16:48:24 -080030738 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
30739 * `$id` function, which tracks items by their identity:
30740 * ```html
30741 * <div ng-repeat="obj in collection track by $id(obj)">
30742 * {{obj.prop}}
30743 * </div>
30744 * ```
30745 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070030746 * <br />
Ed Tanous904063f2017-03-02 16:48:24 -080030747 * <div class="alert alert-warning">
30748 * **Note:** `track by` must always be the last expression:
30749 * </div>
30750 * ```
Ed Tanous4758d5b2017-06-06 15:28:13 -070030751 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
30752 * {{model.name}}
30753 * </div>
Ed Tanous904063f2017-03-02 16:48:24 -080030754 * ```
30755 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070030756 *
Ed Tanous904063f2017-03-02 16:48:24 -080030757 * # Special repeat start and end points
30758 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
30759 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
30760 * 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)
30761 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
30762 *
30763 * The example below makes use of this feature:
30764 * ```html
30765 * <header ng-repeat-start="item in items">
30766 * Header {{ item }}
30767 * </header>
30768 * <div class="body">
30769 * Body {{ item }}
30770 * </div>
30771 * <footer ng-repeat-end>
30772 * Footer {{ item }}
30773 * </footer>
30774 * ```
30775 *
30776 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
30777 * ```html
30778 * <header>
30779 * Header A
30780 * </header>
30781 * <div class="body">
30782 * Body A
30783 * </div>
30784 * <footer>
30785 * Footer A
30786 * </footer>
30787 * <header>
30788 * Header B
30789 * </header>
30790 * <div class="body">
30791 * Body B
30792 * </div>
30793 * <footer>
30794 * Footer B
30795 * </footer>
30796 * ```
30797 *
30798 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
30799 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
30800 *
30801 * @animations
30802 * | Animation | Occurs |
30803 * |----------------------------------|-------------------------------------|
30804 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter |
30805 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out |
30806 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered |
30807 *
30808 * See the example below for defining CSS animations with ngRepeat.
30809 *
30810 * @element ANY
30811 * @scope
30812 * @priority 1000
30813 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
30814 * formats are currently supported:
30815 *
30816 * * `variable in expression` – where variable is the user defined loop variable and `expression`
30817 * is a scope expression giving the collection to enumerate.
30818 *
30819 * For example: `album in artist.albums`.
30820 *
30821 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
30822 * and `expression` is the scope expression giving the collection to enumerate.
30823 *
30824 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
30825 *
30826 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
30827 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
30828 * is specified, ng-repeat associates elements by identity. It is an error to have
30829 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
30830 * mapped to the same DOM element, which is not possible.)
30831 *
30832 * Note that the tracking expression must come last, after any filters, and the alias expression.
30833 *
30834 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
30835 * will be associated by item identity in the array.
30836 *
30837 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
30838 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
30839 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
30840 * element in the same way in the DOM.
30841 *
30842 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
30843 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
30844 * property is same.
30845 *
30846 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
30847 * to items in conjunction with a tracking expression.
30848 *
30849 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
30850 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
30851 * when a filter is active on the repeater, but the filtered result set is empty.
30852 *
30853 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
30854 * the items have been processed through the filter.
30855 *
30856 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end
30857 * (and not as operator, inside an expression).
30858 *
30859 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
30860 *
30861 * @example
30862 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed
Ed Tanous4758d5b2017-06-06 15:28:13 -070030863 * results by name or by age. New (entering) and removed (leaving) items are animated.
30864 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true" name="ng-repeat">
Ed Tanous904063f2017-03-02 16:48:24 -080030865 <file name="index.html">
30866 <div ng-controller="repeatController">
30867 I have {{friends.length}} friends. They are:
30868 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
30869 <ul class="example-animate-container">
30870 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
30871 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
30872 </li>
Ed Tanous4758d5b2017-06-06 15:28:13 -070030873 <li class="animate-repeat" ng-if="results.length === 0">
Ed Tanous904063f2017-03-02 16:48:24 -080030874 <strong>No results found...</strong>
30875 </li>
30876 </ul>
30877 </div>
30878 </file>
30879 <file name="script.js">
30880 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
30881 $scope.friends = [
30882 {name:'John', age:25, gender:'boy'},
30883 {name:'Jessie', age:30, gender:'girl'},
30884 {name:'Johanna', age:28, gender:'girl'},
30885 {name:'Joy', age:15, gender:'girl'},
30886 {name:'Mary', age:28, gender:'girl'},
30887 {name:'Peter', age:95, gender:'boy'},
30888 {name:'Sebastian', age:50, gender:'boy'},
30889 {name:'Erika', age:27, gender:'girl'},
30890 {name:'Patrick', age:40, gender:'boy'},
30891 {name:'Samantha', age:60, gender:'girl'}
30892 ];
30893 });
30894 </file>
30895 <file name="animations.css">
30896 .example-animate-container {
30897 background:white;
30898 border:1px solid black;
30899 list-style:none;
30900 margin:0;
30901 padding:0 10px;
30902 }
30903
30904 .animate-repeat {
30905 line-height:30px;
30906 list-style:none;
30907 box-sizing:border-box;
30908 }
30909
30910 .animate-repeat.ng-move,
30911 .animate-repeat.ng-enter,
30912 .animate-repeat.ng-leave {
30913 transition:all linear 0.5s;
30914 }
30915
30916 .animate-repeat.ng-leave.ng-leave-active,
30917 .animate-repeat.ng-move,
30918 .animate-repeat.ng-enter {
30919 opacity:0;
30920 max-height:0;
30921 }
30922
30923 .animate-repeat.ng-leave,
30924 .animate-repeat.ng-move.ng-move-active,
30925 .animate-repeat.ng-enter.ng-enter-active {
30926 opacity:1;
30927 max-height:30px;
30928 }
30929 </file>
30930 <file name="protractor.js" type="protractor">
30931 var friends = element.all(by.repeater('friend in friends'));
30932
30933 it('should render initial data set', function() {
30934 expect(friends.count()).toBe(10);
30935 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
30936 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
30937 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
30938 expect(element(by.binding('friends.length')).getText())
30939 .toMatch("I have 10 friends. They are:");
30940 });
30941
30942 it('should update repeater when filter predicate changes', function() {
30943 expect(friends.count()).toBe(10);
30944
30945 element(by.model('q')).sendKeys('ma');
30946
30947 expect(friends.count()).toBe(2);
30948 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
30949 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
30950 });
30951 </file>
30952 </example>
30953 */
30954var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) {
30955 var NG_REMOVED = '$$NG_REMOVED';
30956 var ngRepeatMinErr = minErr('ngRepeat');
30957
30958 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
30959 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
30960 scope[valueIdentifier] = value;
30961 if (keyIdentifier) scope[keyIdentifier] = key;
30962 scope.$index = index;
30963 scope.$first = (index === 0);
30964 scope.$last = (index === (arrayLength - 1));
30965 scope.$middle = !(scope.$first || scope.$last);
Ed Tanous4758d5b2017-06-06 15:28:13 -070030966 // eslint-disable-next-line no-bitwise
30967 scope.$odd = !(scope.$even = (index & 1) === 0);
Ed Tanous904063f2017-03-02 16:48:24 -080030968 };
30969
30970 var getBlockStart = function(block) {
30971 return block.clone[0];
30972 };
30973
30974 var getBlockEnd = function(block) {
30975 return block.clone[block.clone.length - 1];
30976 };
30977
30978
30979 return {
30980 restrict: 'A',
30981 multiElement: true,
30982 transclude: 'element',
30983 priority: 1000,
30984 terminal: true,
30985 $$tlb: true,
30986 compile: function ngRepeatCompile($element, $attr) {
30987 var expression = $attr.ngRepeat;
30988 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression);
30989
30990 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*$/);
30991
30992 if (!match) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070030993 throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.',
Ed Tanous904063f2017-03-02 16:48:24 -080030994 expression);
30995 }
30996
30997 var lhs = match[1];
30998 var rhs = match[2];
30999 var aliasAs = match[3];
31000 var trackByExp = match[4];
31001
Ed Tanous4758d5b2017-06-06 15:28:13 -070031002 match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);
Ed Tanous904063f2017-03-02 16:48:24 -080031003
31004 if (!match) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031005 throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.',
Ed Tanous904063f2017-03-02 16:48:24 -080031006 lhs);
31007 }
31008 var valueIdentifier = match[3] || match[1];
31009 var keyIdentifier = match[2];
31010
31011 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
31012 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031013 throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.',
Ed Tanous904063f2017-03-02 16:48:24 -080031014 aliasAs);
31015 }
31016
31017 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
31018 var hashFnLocals = {$id: hashKey};
31019
31020 if (trackByExp) {
31021 trackByExpGetter = $parse(trackByExp);
31022 } else {
31023 trackByIdArrayFn = function(key, value) {
31024 return hashKey(value);
31025 };
31026 trackByIdObjFn = function(key) {
31027 return key;
31028 };
31029 }
31030
31031 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
31032
31033 if (trackByExpGetter) {
31034 trackByIdExpFn = function(key, value, index) {
31035 // assign key, value, and $index to the locals so that they can be used in hash functions
31036 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
31037 hashFnLocals[valueIdentifier] = value;
31038 hashFnLocals.$index = index;
31039 return trackByExpGetter($scope, hashFnLocals);
31040 };
31041 }
31042
31043 // Store a list of elements from previous run. This is a hash where key is the item from the
31044 // iterator, and the value is objects with following properties.
31045 // - scope: bound scope
31046 // - element: previous element.
31047 // - index: position
31048 //
31049 // We are using no-proto object so that we don't need to guard against inherited props via
31050 // hasOwnProperty.
31051 var lastBlockMap = createMap();
31052
31053 //watch props
31054 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
31055 var index, length,
31056 previousNode = $element[0], // node that cloned nodes should be inserted after
31057 // initialized to the comment node anchor
31058 nextNode,
31059 // Same as lastBlockMap but it has the current state. It will become the
31060 // lastBlockMap on the next iteration.
31061 nextBlockMap = createMap(),
31062 collectionLength,
31063 key, value, // key/value of iteration
31064 trackById,
31065 trackByIdFn,
31066 collectionKeys,
31067 block, // last object information {scope, element, id}
31068 nextBlockOrder,
31069 elementsToRemove;
31070
31071 if (aliasAs) {
31072 $scope[aliasAs] = collection;
31073 }
31074
31075 if (isArrayLike(collection)) {
31076 collectionKeys = collection;
31077 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
31078 } else {
31079 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
31080 // if object, extract keys, in enumeration order, unsorted
31081 collectionKeys = [];
31082 for (var itemKey in collection) {
31083 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
31084 collectionKeys.push(itemKey);
31085 }
31086 }
31087 }
31088
31089 collectionLength = collectionKeys.length;
31090 nextBlockOrder = new Array(collectionLength);
31091
31092 // locate existing items
31093 for (index = 0; index < collectionLength; index++) {
31094 key = (collection === collectionKeys) ? index : collectionKeys[index];
31095 value = collection[key];
31096 trackById = trackByIdFn(key, value, index);
31097 if (lastBlockMap[trackById]) {
31098 // found previously seen block
31099 block = lastBlockMap[trackById];
31100 delete lastBlockMap[trackById];
31101 nextBlockMap[trackById] = block;
31102 nextBlockOrder[index] = block;
31103 } else if (nextBlockMap[trackById]) {
31104 // if collision detected. restore lastBlockMap and throw an error
31105 forEach(nextBlockOrder, function(block) {
31106 if (block && block.scope) lastBlockMap[block.id] = block;
31107 });
31108 throw ngRepeatMinErr('dupes',
Ed Tanous4758d5b2017-06-06 15:28:13 -070031109 'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}',
Ed Tanous904063f2017-03-02 16:48:24 -080031110 expression, trackById, value);
31111 } else {
31112 // new never before seen block
31113 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
31114 nextBlockMap[trackById] = true;
31115 }
31116 }
31117
31118 // remove leftover items
31119 for (var blockKey in lastBlockMap) {
31120 block = lastBlockMap[blockKey];
31121 elementsToRemove = getBlockNodes(block.clone);
31122 $animate.leave(elementsToRemove);
31123 if (elementsToRemove[0].parentNode) {
31124 // if the element was not removed yet because of pending animation, mark it as deleted
31125 // so that we can ignore it later
31126 for (index = 0, length = elementsToRemove.length; index < length; index++) {
31127 elementsToRemove[index][NG_REMOVED] = true;
31128 }
31129 }
31130 block.scope.$destroy();
31131 }
31132
31133 // we are not using forEach for perf reasons (trying to avoid #call)
31134 for (index = 0; index < collectionLength; index++) {
31135 key = (collection === collectionKeys) ? index : collectionKeys[index];
31136 value = collection[key];
31137 block = nextBlockOrder[index];
31138
31139 if (block.scope) {
31140 // if we have already seen this object, then we need to reuse the
31141 // associated scope/element
31142
31143 nextNode = previousNode;
31144
31145 // skip nodes that are already pending removal via leave animation
31146 do {
31147 nextNode = nextNode.nextSibling;
31148 } while (nextNode && nextNode[NG_REMOVED]);
31149
Ed Tanous4758d5b2017-06-06 15:28:13 -070031150 if (getBlockStart(block) !== nextNode) {
Ed Tanous904063f2017-03-02 16:48:24 -080031151 // existing item which got moved
31152 $animate.move(getBlockNodes(block.clone), null, previousNode);
31153 }
31154 previousNode = getBlockEnd(block);
31155 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
31156 } else {
31157 // new item which we don't know about
31158 $transclude(function ngRepeatTransclude(clone, scope) {
31159 block.scope = scope;
31160 // http://jsperf.com/clone-vs-createcomment
31161 var endNode = ngRepeatEndComment.cloneNode(false);
31162 clone[clone.length++] = endNode;
31163
31164 $animate.enter(clone, null, previousNode);
31165 previousNode = endNode;
31166 // Note: We only need the first/last node of the cloned nodes.
31167 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
31168 // by a directive with templateUrl when its template arrives.
31169 block.clone = clone;
31170 nextBlockMap[block.id] = block;
31171 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
31172 });
31173 }
31174 }
31175 lastBlockMap = nextBlockMap;
31176 });
31177 };
31178 }
31179 };
31180}];
31181
31182var NG_HIDE_CLASS = 'ng-hide';
31183var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
31184/**
31185 * @ngdoc directive
31186 * @name ngShow
31187 * @multiElement
31188 *
31189 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070031190 * The `ngShow` directive shows or hides the given HTML element based on the expression provided to
31191 * the `ngShow` attribute.
31192 *
31193 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
31194 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
31195 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
31196 * {@link ng.directive:ngCsp ngCsp}).
Ed Tanous904063f2017-03-02 16:48:24 -080031197 *
31198 * ```html
31199 * <!-- when $scope.myValue is truthy (element is visible) -->
31200 * <div ng-show="myValue"></div>
31201 *
31202 * <!-- when $scope.myValue is falsy (element is hidden) -->
31203 * <div ng-show="myValue" class="ng-hide"></div>
31204 * ```
31205 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031206 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added
31207 * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide`
31208 * CSS class is removed from the element causing the element not to appear hidden.
Ed Tanous904063f2017-03-02 16:48:24 -080031209 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031210 * ## Why is `!important` used?
Ed Tanous904063f2017-03-02 16:48:24 -080031211 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031212 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
31213 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
31214 * simple as changing the display style on a HTML list item would make hidden elements appear
31215 * visible. This also becomes a bigger issue when dealing with CSS frameworks.
Ed Tanous904063f2017-03-02 16:48:24 -080031216 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031217 * By using `!important`, the show and hide behavior will work as expected despite any clash between
31218 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
31219 * developer chooses to override the styling to change how to hide an element then it is just a
31220 * matter of using `!important` in their own CSS code.
Ed Tanous904063f2017-03-02 16:48:24 -080031221 *
31222 * ### Overriding `.ng-hide`
31223 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031224 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you
31225 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
31226 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
31227 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
Ed Tanous904063f2017-03-02 16:48:24 -080031228 *
31229 * ```css
31230 * .ng-hide:not(.ng-hide-animate) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031231 * /&#42; These are just alternative ways of hiding an element &#42;/
Ed Tanous904063f2017-03-02 16:48:24 -080031232 * display: block!important;
31233 * position: absolute;
31234 * top: -9999px;
31235 * left: -9999px;
31236 * }
31237 * ```
31238 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031239 * By default you don't need to override anything in CSS and the animations will work around the
31240 * display style.
Ed Tanous904063f2017-03-02 16:48:24 -080031241 *
31242 * ## A note about animations with `ngShow`
31243 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031244 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
31245 * directive expression is true and false. This system works like the animation system present with
31246 * `ngClass` except that you must also include the `!important` flag to override the display
31247 * property so that the elements are not actually hidden during the animation.
Ed Tanous904063f2017-03-02 16:48:24 -080031248 *
31249 * ```css
Ed Tanous4758d5b2017-06-06 15:28:13 -070031250 * /&#42; A working example can be found at the bottom of this page. &#42;/
Ed Tanous904063f2017-03-02 16:48:24 -080031251 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031252 * transition: all 0.5s linear;
Ed Tanous904063f2017-03-02 16:48:24 -080031253 * }
31254 *
31255 * .my-element.ng-hide-add { ... }
31256 * .my-element.ng-hide-add.ng-hide-add-active { ... }
31257 * .my-element.ng-hide-remove { ... }
31258 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
31259 * ```
31260 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031261 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
31262 * to block during animation states - ngAnimate will automatically handle the style toggling for you.
Ed Tanous904063f2017-03-02 16:48:24 -080031263 *
31264 * @animations
Ed Tanous4758d5b2017-06-06 15:28:13 -070031265 * | Animation | Occurs |
31266 * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
31267 * | {@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. |
31268 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. |
Ed Tanous904063f2017-03-02 16:48:24 -080031269 *
31270 * @element ANY
Ed Tanous4758d5b2017-06-06 15:28:13 -070031271 * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the
31272 * element is shown/hidden respectively.
Ed Tanous904063f2017-03-02 16:48:24 -080031273 *
31274 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070031275 * A simple example, animating the element's opacity:
31276 *
31277 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple">
Ed Tanous904063f2017-03-02 16:48:24 -080031278 <file name="index.html">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031279 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
31280 <div class="check-element animate-show-hide" ng-show="checked">
31281 I show up when your checkbox is checked.
Ed Tanous904063f2017-03-02 16:48:24 -080031282 </div>
Ed Tanous904063f2017-03-02 16:48:24 -080031283 </file>
31284 <file name="animations.css">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031285 .animate-show-hide.ng-hide {
31286 opacity: 0;
Ed Tanous904063f2017-03-02 16:48:24 -080031287 }
31288
Ed Tanous4758d5b2017-06-06 15:28:13 -070031289 .animate-show-hide.ng-hide-add,
31290 .animate-show-hide.ng-hide-remove {
Ed Tanous904063f2017-03-02 16:48:24 -080031291 transition: all linear 0.5s;
31292 }
31293
Ed Tanous904063f2017-03-02 16:48:24 -080031294 .check-element {
Ed Tanous904063f2017-03-02 16:48:24 -080031295 border: 1px solid black;
Ed Tanous4758d5b2017-06-06 15:28:13 -070031296 opacity: 1;
31297 padding: 10px;
Ed Tanous904063f2017-03-02 16:48:24 -080031298 }
31299 </file>
31300 <file name="protractor.js" type="protractor">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031301 it('should check ngShow', function() {
31302 var checkbox = element(by.model('checked'));
31303 var checkElem = element(by.css('.check-element'));
Ed Tanous904063f2017-03-02 16:48:24 -080031304
Ed Tanous4758d5b2017-06-06 15:28:13 -070031305 expect(checkElem.isDisplayed()).toBe(false);
31306 checkbox.click();
31307 expect(checkElem.isDisplayed()).toBe(true);
31308 });
31309 </file>
31310 </example>
31311 *
31312 * <hr />
31313 * @example
31314 * A more complex example, featuring different show/hide animations:
31315 *
31316 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex">
31317 <file name="index.html">
31318 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
31319 <div class="check-element funky-show-hide" ng-show="checked">
31320 I show up when your checkbox is checked.
31321 </div>
31322 </file>
31323 <file name="animations.css">
31324 body {
31325 overflow: hidden;
31326 perspective: 1000px;
31327 }
Ed Tanous904063f2017-03-02 16:48:24 -080031328
Ed Tanous4758d5b2017-06-06 15:28:13 -070031329 .funky-show-hide.ng-hide-add {
31330 transform: rotateZ(0);
31331 transform-origin: right;
31332 transition: all 0.5s ease-in-out;
31333 }
Ed Tanous904063f2017-03-02 16:48:24 -080031334
Ed Tanous4758d5b2017-06-06 15:28:13 -070031335 .funky-show-hide.ng-hide-add.ng-hide-add-active {
31336 transform: rotateZ(-135deg);
31337 }
31338
31339 .funky-show-hide.ng-hide-remove {
31340 transform: rotateY(90deg);
31341 transform-origin: left;
31342 transition: all 0.5s ease;
31343 }
31344
31345 .funky-show-hide.ng-hide-remove.ng-hide-remove-active {
31346 transform: rotateY(0);
31347 }
31348
31349 .check-element {
31350 border: 1px solid black;
31351 opacity: 1;
31352 padding: 10px;
31353 }
31354 </file>
31355 <file name="protractor.js" type="protractor">
31356 it('should check ngShow', function() {
31357 var checkbox = element(by.model('checked'));
31358 var checkElem = element(by.css('.check-element'));
31359
31360 expect(checkElem.isDisplayed()).toBe(false);
31361 checkbox.click();
31362 expect(checkElem.isDisplayed()).toBe(true);
Ed Tanous904063f2017-03-02 16:48:24 -080031363 });
31364 </file>
31365 </example>
31366 */
31367var ngShowDirective = ['$animate', function($animate) {
31368 return {
31369 restrict: 'A',
31370 multiElement: true,
31371 link: function(scope, element, attr) {
31372 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
31373 // we're adding a temporary, animation-specific class for ng-hide since this way
31374 // we can control when the element is actually displayed on screen without having
31375 // to have a global/greedy CSS selector that breaks when other animations are run.
31376 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
31377 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
31378 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
31379 });
31380 });
31381 }
31382 };
31383}];
31384
31385
31386/**
31387 * @ngdoc directive
31388 * @name ngHide
31389 * @multiElement
31390 *
31391 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070031392 * The `ngHide` directive shows or hides the given HTML element based on the expression provided to
31393 * the `ngHide` attribute.
31394 *
31395 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
31396 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
31397 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
31398 * {@link ng.directive:ngCsp ngCsp}).
Ed Tanous904063f2017-03-02 16:48:24 -080031399 *
31400 * ```html
31401 * <!-- when $scope.myValue is truthy (element is hidden) -->
31402 * <div ng-hide="myValue" class="ng-hide"></div>
31403 *
31404 * <!-- when $scope.myValue is falsy (element is visible) -->
31405 * <div ng-hide="myValue"></div>
31406 * ```
31407 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031408 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added
31409 * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide`
31410 * CSS class is removed from the element causing the element not to appear hidden.
Ed Tanous904063f2017-03-02 16:48:24 -080031411 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031412 * ## Why is `!important` used?
Ed Tanous904063f2017-03-02 16:48:24 -080031413 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031414 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
31415 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
31416 * simple as changing the display style on a HTML list item would make hidden elements appear
31417 * visible. This also becomes a bigger issue when dealing with CSS frameworks.
Ed Tanous904063f2017-03-02 16:48:24 -080031418 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031419 * By using `!important`, the show and hide behavior will work as expected despite any clash between
31420 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
31421 * developer chooses to override the styling to change how to hide an element then it is just a
31422 * matter of using `!important` in their own CSS code.
Ed Tanous904063f2017-03-02 16:48:24 -080031423 *
31424 * ### Overriding `.ng-hide`
31425 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031426 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you
31427 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
31428 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
31429 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
Ed Tanous904063f2017-03-02 16:48:24 -080031430 *
31431 * ```css
Ed Tanous4758d5b2017-06-06 15:28:13 -070031432 * .ng-hide:not(.ng-hide-animate) {
31433 * /&#42; These are just alternative ways of hiding an element &#42;/
Ed Tanous904063f2017-03-02 16:48:24 -080031434 * display: block!important;
31435 * position: absolute;
31436 * top: -9999px;
31437 * left: -9999px;
31438 * }
31439 * ```
31440 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031441 * By default you don't need to override in CSS anything and the animations will work around the
31442 * display style.
Ed Tanous904063f2017-03-02 16:48:24 -080031443 *
31444 * ## A note about animations with `ngHide`
31445 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031446 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
31447 * directive expression is true and false. This system works like the animation system present with
31448 * `ngClass` except that you must also include the `!important` flag to override the display
31449 * property so that the elements are not actually hidden during the animation.
Ed Tanous904063f2017-03-02 16:48:24 -080031450 *
31451 * ```css
Ed Tanous4758d5b2017-06-06 15:28:13 -070031452 * /&#42; A working example can be found at the bottom of this page. &#42;/
Ed Tanous904063f2017-03-02 16:48:24 -080031453 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031454 * transition: all 0.5s linear;
Ed Tanous904063f2017-03-02 16:48:24 -080031455 * }
31456 *
31457 * .my-element.ng-hide-add { ... }
31458 * .my-element.ng-hide-add.ng-hide-add-active { ... }
31459 * .my-element.ng-hide-remove { ... }
31460 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
31461 * ```
31462 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031463 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
31464 * to block during animation states - ngAnimate will automatically handle the style toggling for you.
Ed Tanous904063f2017-03-02 16:48:24 -080031465 *
31466 * @animations
Ed Tanous4758d5b2017-06-06 15:28:13 -070031467 * | Animation | Occurs |
31468 * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------|
31469 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. |
31470 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. |
Ed Tanous904063f2017-03-02 16:48:24 -080031471 *
31472 *
31473 * @element ANY
Ed Tanous4758d5b2017-06-06 15:28:13 -070031474 * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the
31475 * element is hidden/shown respectively.
Ed Tanous904063f2017-03-02 16:48:24 -080031476 *
31477 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070031478 * A simple example, animating the element's opacity:
31479 *
31480 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple">
Ed Tanous904063f2017-03-02 16:48:24 -080031481 <file name="index.html">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031482 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
31483 <div class="check-element animate-show-hide" ng-hide="checked">
31484 I hide when your checkbox is checked.
Ed Tanous904063f2017-03-02 16:48:24 -080031485 </div>
Ed Tanous904063f2017-03-02 16:48:24 -080031486 </file>
31487 <file name="animations.css">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031488 .animate-show-hide.ng-hide {
31489 opacity: 0;
Ed Tanous904063f2017-03-02 16:48:24 -080031490 }
31491
Ed Tanous4758d5b2017-06-06 15:28:13 -070031492 .animate-show-hide.ng-hide-add,
31493 .animate-show-hide.ng-hide-remove {
31494 transition: all linear 0.5s;
Ed Tanous904063f2017-03-02 16:48:24 -080031495 }
31496
31497 .check-element {
Ed Tanous904063f2017-03-02 16:48:24 -080031498 border: 1px solid black;
Ed Tanous4758d5b2017-06-06 15:28:13 -070031499 opacity: 1;
31500 padding: 10px;
Ed Tanous904063f2017-03-02 16:48:24 -080031501 }
31502 </file>
31503 <file name="protractor.js" type="protractor">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031504 it('should check ngHide', function() {
31505 var checkbox = element(by.model('checked'));
31506 var checkElem = element(by.css('.check-element'));
Ed Tanous904063f2017-03-02 16:48:24 -080031507
Ed Tanous4758d5b2017-06-06 15:28:13 -070031508 expect(checkElem.isDisplayed()).toBe(true);
31509 checkbox.click();
31510 expect(checkElem.isDisplayed()).toBe(false);
31511 });
31512 </file>
31513 </example>
31514 *
31515 * <hr />
31516 * @example
31517 * A more complex example, featuring different show/hide animations:
31518 *
31519 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex">
31520 <file name="index.html">
31521 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
31522 <div class="check-element funky-show-hide" ng-hide="checked">
31523 I hide when your checkbox is checked.
31524 </div>
31525 </file>
31526 <file name="animations.css">
31527 body {
31528 overflow: hidden;
31529 perspective: 1000px;
31530 }
Ed Tanous904063f2017-03-02 16:48:24 -080031531
Ed Tanous4758d5b2017-06-06 15:28:13 -070031532 .funky-show-hide.ng-hide-add {
31533 transform: rotateZ(0);
31534 transform-origin: right;
31535 transition: all 0.5s ease-in-out;
31536 }
Ed Tanous904063f2017-03-02 16:48:24 -080031537
Ed Tanous4758d5b2017-06-06 15:28:13 -070031538 .funky-show-hide.ng-hide-add.ng-hide-add-active {
31539 transform: rotateZ(-135deg);
31540 }
31541
31542 .funky-show-hide.ng-hide-remove {
31543 transform: rotateY(90deg);
31544 transform-origin: left;
31545 transition: all 0.5s ease;
31546 }
31547
31548 .funky-show-hide.ng-hide-remove.ng-hide-remove-active {
31549 transform: rotateY(0);
31550 }
31551
31552 .check-element {
31553 border: 1px solid black;
31554 opacity: 1;
31555 padding: 10px;
31556 }
31557 </file>
31558 <file name="protractor.js" type="protractor">
31559 it('should check ngHide', function() {
31560 var checkbox = element(by.model('checked'));
31561 var checkElem = element(by.css('.check-element'));
31562
31563 expect(checkElem.isDisplayed()).toBe(true);
31564 checkbox.click();
31565 expect(checkElem.isDisplayed()).toBe(false);
Ed Tanous904063f2017-03-02 16:48:24 -080031566 });
31567 </file>
31568 </example>
31569 */
31570var ngHideDirective = ['$animate', function($animate) {
31571 return {
31572 restrict: 'A',
31573 multiElement: true,
31574 link: function(scope, element, attr) {
31575 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
31576 // The comment inside of the ngShowDirective explains why we add and
31577 // remove a temporary class for the show/hide animation
31578 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
31579 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
31580 });
31581 });
31582 }
31583 };
31584}];
31585
31586/**
31587 * @ngdoc directive
31588 * @name ngStyle
31589 * @restrict AC
31590 *
31591 * @description
31592 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
31593 *
31594 * @knownIssue
31595 * You should not use {@link guide/interpolation interpolation} in the value of the `style`
31596 * attribute, when using the `ngStyle` directive on the same element.
31597 * See {@link guide/interpolation#known-issues here} for more info.
31598 *
31599 * @element ANY
31600 * @param {expression} ngStyle
31601 *
31602 * {@link guide/expression Expression} which evals to an
31603 * object whose keys are CSS style names and values are corresponding values for those CSS
31604 * keys.
31605 *
31606 * Since some CSS style names are not valid keys for an object, they must be quoted.
31607 * See the 'background-color' style in the example below.
31608 *
31609 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070031610 <example name="ng-style">
Ed Tanous904063f2017-03-02 16:48:24 -080031611 <file name="index.html">
31612 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
31613 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
31614 <input type="button" value="clear" ng-click="myStyle={}">
31615 <br/>
31616 <span ng-style="myStyle">Sample Text</span>
31617 <pre>myStyle={{myStyle}}</pre>
31618 </file>
31619 <file name="style.css">
31620 span {
31621 color: black;
31622 }
31623 </file>
31624 <file name="protractor.js" type="protractor">
31625 var colorSpan = element(by.css('span'));
31626
31627 it('should check ng-style', function() {
31628 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
31629 element(by.css('input[value=\'set color\']')).click();
31630 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
31631 element(by.css('input[value=clear]')).click();
31632 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
31633 });
31634 </file>
31635 </example>
31636 */
31637var ngStyleDirective = ngDirective(function(scope, element, attr) {
31638 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
31639 if (oldStyles && (newStyles !== oldStyles)) {
31640 forEach(oldStyles, function(val, style) { element.css(style, '');});
31641 }
31642 if (newStyles) element.css(newStyles);
31643 }, true);
31644});
31645
31646/**
31647 * @ngdoc directive
31648 * @name ngSwitch
31649 * @restrict EA
31650 *
31651 * @description
31652 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
31653 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
31654 * as specified in the template.
31655 *
31656 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
31657 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
31658 * matches the value obtained from the evaluated expression. In other words, you define a container element
31659 * (where you place the directive), place an expression on the **`on="..."` attribute**
31660 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
31661 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
31662 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
31663 * attribute is displayed.
31664 *
31665 * <div class="alert alert-info">
31666 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
31667 * as literal string values to match against.
31668 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
31669 * value of the expression `$scope.someVal`.
31670 * </div>
31671
31672 * @animations
31673 * | Animation | Occurs |
31674 * |----------------------------------|-------------------------------------|
31675 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container |
31676 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM |
31677 *
31678 * @usage
31679 *
31680 * ```
31681 * <ANY ng-switch="expression">
31682 * <ANY ng-switch-when="matchValue1">...</ANY>
31683 * <ANY ng-switch-when="matchValue2">...</ANY>
31684 * <ANY ng-switch-default>...</ANY>
31685 * </ANY>
31686 * ```
31687 *
31688 *
31689 * @scope
31690 * @priority 1200
31691 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
31692 * On child elements add:
31693 *
31694 * * `ngSwitchWhen`: the case statement to match against. If match then this
31695 * case will be displayed. If the same match appears multiple times, all the
Ed Tanous4758d5b2017-06-06 15:28:13 -070031696 * elements will be displayed. It is possible to associate multiple values to
31697 * the same `ngSwitchWhen` by defining the optional attribute
31698 * `ngSwitchWhenSeparator`. The separator will be used to split the value of
31699 * the `ngSwitchWhen` attribute into multiple tokens, and the element will show
31700 * if any of the `ngSwitch` evaluates to any of these tokens.
Ed Tanous904063f2017-03-02 16:48:24 -080031701 * * `ngSwitchDefault`: the default case when no other case match. If there
31702 * are multiple default cases, all of them will be displayed when no other
31703 * case match.
31704 *
31705 *
31706 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070031707 <example module="switchExample" deps="angular-animate.js" animations="true" name="ng-switch">
Ed Tanous904063f2017-03-02 16:48:24 -080031708 <file name="index.html">
31709 <div ng-controller="ExampleController">
31710 <select ng-model="selection" ng-options="item for item in items">
31711 </select>
31712 <code>selection={{selection}}</code>
31713 <hr/>
31714 <div class="animate-switch-container"
31715 ng-switch on="selection">
Ed Tanous4758d5b2017-06-06 15:28:13 -070031716 <div class="animate-switch" ng-switch-when="settings|options" ng-switch-when-separator="|">Settings Div</div>
Ed Tanous904063f2017-03-02 16:48:24 -080031717 <div class="animate-switch" ng-switch-when="home">Home Span</div>
31718 <div class="animate-switch" ng-switch-default>default</div>
31719 </div>
31720 </div>
31721 </file>
31722 <file name="script.js">
31723 angular.module('switchExample', ['ngAnimate'])
31724 .controller('ExampleController', ['$scope', function($scope) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031725 $scope.items = ['settings', 'home', 'options', 'other'];
Ed Tanous904063f2017-03-02 16:48:24 -080031726 $scope.selection = $scope.items[0];
31727 }]);
31728 </file>
31729 <file name="animations.css">
31730 .animate-switch-container {
31731 position:relative;
31732 background:white;
31733 border:1px solid black;
31734 height:40px;
31735 overflow:hidden;
31736 }
31737
31738 .animate-switch {
31739 padding:10px;
31740 }
31741
31742 .animate-switch.ng-animate {
31743 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
31744
31745 position:absolute;
31746 top:0;
31747 left:0;
31748 right:0;
31749 bottom:0;
31750 }
31751
31752 .animate-switch.ng-leave.ng-leave-active,
31753 .animate-switch.ng-enter {
31754 top:-50px;
31755 }
31756 .animate-switch.ng-leave,
31757 .animate-switch.ng-enter.ng-enter-active {
31758 top:0;
31759 }
31760 </file>
31761 <file name="protractor.js" type="protractor">
31762 var switchElem = element(by.css('[ng-switch]'));
31763 var select = element(by.model('selection'));
31764
31765 it('should start in settings', function() {
31766 expect(switchElem.getText()).toMatch(/Settings Div/);
31767 });
31768 it('should change to home', function() {
31769 select.all(by.css('option')).get(1).click();
31770 expect(switchElem.getText()).toMatch(/Home Span/);
31771 });
Ed Tanous4758d5b2017-06-06 15:28:13 -070031772 it('should change to settings via "options"', function() {
Ed Tanous904063f2017-03-02 16:48:24 -080031773 select.all(by.css('option')).get(2).click();
Ed Tanous4758d5b2017-06-06 15:28:13 -070031774 expect(switchElem.getText()).toMatch(/Settings Div/);
31775 });
31776 it('should select default', function() {
31777 select.all(by.css('option')).get(3).click();
Ed Tanous904063f2017-03-02 16:48:24 -080031778 expect(switchElem.getText()).toMatch(/default/);
31779 });
31780 </file>
31781 </example>
31782 */
31783var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) {
31784 return {
31785 require: 'ngSwitch',
31786
31787 // asks for $scope to fool the BC controller module
Ed Tanous4758d5b2017-06-06 15:28:13 -070031788 controller: ['$scope', function NgSwitchController() {
Ed Tanous904063f2017-03-02 16:48:24 -080031789 this.cases = {};
31790 }],
31791 link: function(scope, element, attr, ngSwitchController) {
31792 var watchExpr = attr.ngSwitch || attr.on,
31793 selectedTranscludes = [],
31794 selectedElements = [],
31795 previousLeaveAnimations = [],
31796 selectedScopes = [];
31797
31798 var spliceFactory = function(array, index) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031799 return function(response) {
31800 if (response !== false) array.splice(index, 1);
31801 };
Ed Tanous904063f2017-03-02 16:48:24 -080031802 };
31803
31804 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
31805 var i, ii;
Ed Tanous4758d5b2017-06-06 15:28:13 -070031806
31807 // Start with the last, in case the array is modified during the loop
31808 while (previousLeaveAnimations.length) {
31809 $animate.cancel(previousLeaveAnimations.pop());
Ed Tanous904063f2017-03-02 16:48:24 -080031810 }
Ed Tanous904063f2017-03-02 16:48:24 -080031811
31812 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
31813 var selected = getBlockNodes(selectedElements[i].clone);
31814 selectedScopes[i].$destroy();
Ed Tanous4758d5b2017-06-06 15:28:13 -070031815 var runner = previousLeaveAnimations[i] = $animate.leave(selected);
31816 runner.done(spliceFactory(previousLeaveAnimations, i));
Ed Tanous904063f2017-03-02 16:48:24 -080031817 }
31818
31819 selectedElements.length = 0;
31820 selectedScopes.length = 0;
31821
31822 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
31823 forEach(selectedTranscludes, function(selectedTransclude) {
31824 selectedTransclude.transclude(function(caseElement, selectedScope) {
31825 selectedScopes.push(selectedScope);
31826 var anchor = selectedTransclude.element;
31827 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen');
31828 var block = { clone: caseElement };
31829
31830 selectedElements.push(block);
31831 $animate.enter(caseElement, anchor.parent(), anchor);
31832 });
31833 });
31834 }
31835 });
31836 }
31837 };
31838}];
31839
31840var ngSwitchWhenDirective = ngDirective({
31841 transclude: 'element',
31842 priority: 1200,
31843 require: '^ngSwitch',
31844 multiElement: true,
31845 link: function(scope, element, attrs, ctrl, $transclude) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070031846
31847 var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter(
31848 // Filter duplicate cases
31849 function(element, index, array) { return array[index - 1] !== element; }
31850 );
31851
31852 forEach(cases, function(whenCase) {
31853 ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []);
31854 ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element });
31855 });
Ed Tanous904063f2017-03-02 16:48:24 -080031856 }
31857});
31858
31859var ngSwitchDefaultDirective = ngDirective({
31860 transclude: 'element',
31861 priority: 1200,
31862 require: '^ngSwitch',
31863 multiElement: true,
31864 link: function(scope, element, attr, ctrl, $transclude) {
31865 ctrl.cases['?'] = (ctrl.cases['?'] || []);
31866 ctrl.cases['?'].push({ transclude: $transclude, element: element });
31867 }
31868});
31869
31870/**
31871 * @ngdoc directive
31872 * @name ngTransclude
31873 * @restrict EAC
31874 *
31875 * @description
31876 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
31877 *
31878 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name
31879 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute.
31880 *
31881 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing
31882 * content of this element will be removed before the transcluded content is inserted.
Ed Tanous4758d5b2017-06-06 15:28:13 -070031883 * If the transcluded content is empty (or only whitespace), the existing content is left intact. This lets you provide fallback
31884 * content in the case that no transcluded content is provided.
Ed Tanous904063f2017-03-02 16:48:24 -080031885 *
31886 * @element ANY
31887 *
31888 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty
31889 * or its value is the same as the name of the attribute then the default slot is used.
31890 *
31891 * @example
31892 * ### Basic transclusion
31893 * This example demonstrates basic transclusion of content into a component directive.
31894 * <example name="simpleTranscludeExample" module="transcludeExample">
31895 * <file name="index.html">
31896 * <script>
31897 * angular.module('transcludeExample', [])
31898 * .directive('pane', function(){
31899 * return {
31900 * restrict: 'E',
31901 * transclude: true,
31902 * scope: { title:'@' },
31903 * template: '<div style="border: 1px solid black;">' +
31904 * '<div style="background-color: gray">{{title}}</div>' +
31905 * '<ng-transclude></ng-transclude>' +
31906 * '</div>'
31907 * };
31908 * })
31909 * .controller('ExampleController', ['$scope', function($scope) {
31910 * $scope.title = 'Lorem Ipsum';
31911 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
31912 * }]);
31913 * </script>
31914 * <div ng-controller="ExampleController">
31915 * <input ng-model="title" aria-label="title"> <br/>
31916 * <textarea ng-model="text" aria-label="text"></textarea> <br/>
Ed Tanous4758d5b2017-06-06 15:28:13 -070031917 * <pane title="{{title}}"><span>{{text}}</span></pane>
Ed Tanous904063f2017-03-02 16:48:24 -080031918 * </div>
31919 * </file>
31920 * <file name="protractor.js" type="protractor">
31921 * it('should have transcluded', function() {
31922 * var titleElement = element(by.model('title'));
31923 * titleElement.clear();
31924 * titleElement.sendKeys('TITLE');
31925 * var textElement = element(by.model('text'));
31926 * textElement.clear();
31927 * textElement.sendKeys('TEXT');
31928 * expect(element(by.binding('title')).getText()).toEqual('TITLE');
31929 * expect(element(by.binding('text')).getText()).toEqual('TEXT');
31930 * });
31931 * </file>
31932 * </example>
31933 *
31934 * @example
31935 * ### Transclude fallback content
31936 * This example shows how to use `NgTransclude` with fallback content, that
31937 * is displayed if no transcluded content is provided.
31938 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070031939 * <example module="transcludeFallbackContentExample" name="ng-transclude">
Ed Tanous904063f2017-03-02 16:48:24 -080031940 * <file name="index.html">
31941 * <script>
31942 * angular.module('transcludeFallbackContentExample', [])
31943 * .directive('myButton', function(){
31944 * return {
31945 * restrict: 'E',
31946 * transclude: true,
31947 * scope: true,
31948 * template: '<button style="cursor: pointer;">' +
31949 * '<ng-transclude>' +
31950 * '<b style="color: red;">Button1</b>' +
31951 * '</ng-transclude>' +
31952 * '</button>'
31953 * };
31954 * });
31955 * </script>
31956 * <!-- fallback button content -->
31957 * <my-button id="fallback"></my-button>
31958 * <!-- modified button content -->
31959 * <my-button id="modified">
31960 * <i style="color: green;">Button2</i>
31961 * </my-button>
31962 * </file>
31963 * <file name="protractor.js" type="protractor">
31964 * it('should have different transclude element content', function() {
31965 * expect(element(by.id('fallback')).getText()).toBe('Button1');
31966 * expect(element(by.id('modified')).getText()).toBe('Button2');
31967 * });
31968 * </file>
31969 * </example>
31970 *
31971 * @example
31972 * ### Multi-slot transclusion
31973 * This example demonstrates using multi-slot transclusion in a component directive.
31974 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample">
31975 * <file name="index.html">
31976 * <style>
31977 * .title, .footer {
31978 * background-color: gray
31979 * }
31980 * </style>
31981 * <div ng-controller="ExampleController">
31982 * <input ng-model="title" aria-label="title"> <br/>
31983 * <textarea ng-model="text" aria-label="text"></textarea> <br/>
31984 * <pane>
31985 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
31986 * <pane-body><p>{{text}}</p></pane-body>
31987 * </pane>
31988 * </div>
31989 * </file>
31990 * <file name="app.js">
31991 * angular.module('multiSlotTranscludeExample', [])
Ed Tanous4758d5b2017-06-06 15:28:13 -070031992 * .directive('pane', function() {
Ed Tanous904063f2017-03-02 16:48:24 -080031993 * return {
31994 * restrict: 'E',
31995 * transclude: {
31996 * 'title': '?paneTitle',
31997 * 'body': 'paneBody',
31998 * 'footer': '?paneFooter'
31999 * },
32000 * template: '<div style="border: 1px solid black;">' +
32001 * '<div class="title" ng-transclude="title">Fallback Title</div>' +
32002 * '<div ng-transclude="body"></div>' +
32003 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
32004 * '</div>'
32005 * };
32006 * })
32007 * .controller('ExampleController', ['$scope', function($scope) {
32008 * $scope.title = 'Lorem Ipsum';
Ed Tanous4758d5b2017-06-06 15:28:13 -070032009 * $scope.link = 'https://google.com';
Ed Tanous904063f2017-03-02 16:48:24 -080032010 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
32011 * }]);
32012 * </file>
32013 * <file name="protractor.js" type="protractor">
32014 * it('should have transcluded the title and the body', function() {
32015 * var titleElement = element(by.model('title'));
32016 * titleElement.clear();
32017 * titleElement.sendKeys('TITLE');
32018 * var textElement = element(by.model('text'));
32019 * textElement.clear();
32020 * textElement.sendKeys('TEXT');
32021 * expect(element(by.css('.title')).getText()).toEqual('TITLE');
32022 * expect(element(by.binding('text')).getText()).toEqual('TEXT');
32023 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer');
32024 * });
32025 * </file>
32026 * </example>
32027 */
32028var ngTranscludeMinErr = minErr('ngTransclude');
32029var ngTranscludeDirective = ['$compile', function($compile) {
32030 return {
32031 restrict: 'EAC',
32032 terminal: true,
32033 compile: function ngTranscludeCompile(tElement) {
32034
32035 // Remove and cache any original content to act as a fallback
32036 var fallbackLinkFn = $compile(tElement.contents());
32037 tElement.empty();
32038
32039 return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) {
32040
32041 if (!$transclude) {
32042 throw ngTranscludeMinErr('orphan',
32043 'Illegal use of ngTransclude directive in the template! ' +
32044 'No parent directive that requires a transclusion found. ' +
32045 'Element: {0}',
32046 startingTag($element));
32047 }
32048
32049
32050 // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default
32051 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
32052 $attrs.ngTransclude = '';
32053 }
32054 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
32055
32056 // If the slot is required and no transclusion content is provided then this call will throw an error
32057 $transclude(ngTranscludeCloneAttachFn, null, slotName);
32058
32059 // If the slot is optional and no transclusion content is provided then use the fallback content
32060 if (slotName && !$transclude.isSlotFilled(slotName)) {
32061 useFallbackContent();
32062 }
32063
32064 function ngTranscludeCloneAttachFn(clone, transcludedScope) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032065 if (clone.length && notWhitespace(clone)) {
Ed Tanous904063f2017-03-02 16:48:24 -080032066 $element.append(clone);
32067 } else {
32068 useFallbackContent();
32069 // There is nothing linked against the transcluded scope since no content was available,
32070 // so it should be safe to clean up the generated scope.
32071 transcludedScope.$destroy();
32072 }
32073 }
32074
32075 function useFallbackContent() {
32076 // Since this is the fallback content rather than the transcluded content,
32077 // we link against the scope of this directive rather than the transcluded scope
32078 fallbackLinkFn($scope, function(clone) {
32079 $element.append(clone);
32080 });
32081 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070032082
32083 function notWhitespace(nodes) {
32084 for (var i = 0, ii = nodes.length; i < ii; i++) {
32085 var node = nodes[i];
32086 if (node.nodeType !== NODE_TYPE_TEXT || node.nodeValue.trim()) {
32087 return true;
32088 }
32089 }
32090 }
Ed Tanous904063f2017-03-02 16:48:24 -080032091 };
32092 }
32093 };
32094}];
32095
32096/**
32097 * @ngdoc directive
32098 * @name script
32099 * @restrict E
32100 *
32101 * @description
32102 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
32103 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
32104 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
32105 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
32106 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
32107 *
32108 * @param {string} type Must be set to `'text/ng-template'`.
32109 * @param {string} id Cache name of the template.
32110 *
32111 * @example
Ed Tanous4758d5b2017-06-06 15:28:13 -070032112 <example name="script-tag">
Ed Tanous904063f2017-03-02 16:48:24 -080032113 <file name="index.html">
32114 <script type="text/ng-template" id="/tpl.html">
32115 Content of the template.
32116 </script>
32117
32118 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
32119 <div id="tpl-content" ng-include src="currentTpl"></div>
32120 </file>
32121 <file name="protractor.js" type="protractor">
32122 it('should load template defined inside script tag', function() {
32123 element(by.css('#tpl-link')).click();
32124 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
32125 });
32126 </file>
32127 </example>
32128 */
32129var scriptDirective = ['$templateCache', function($templateCache) {
32130 return {
32131 restrict: 'E',
32132 terminal: true,
32133 compile: function(element, attr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032134 if (attr.type === 'text/ng-template') {
Ed Tanous904063f2017-03-02 16:48:24 -080032135 var templateUrl = attr.id,
32136 text = element[0].text;
32137
32138 $templateCache.put(templateUrl, text);
32139 }
32140 }
32141 };
32142}];
32143
Ed Tanous4758d5b2017-06-06 15:28:13 -070032144/* exported selectDirective, optionDirective */
32145
Ed Tanous904063f2017-03-02 16:48:24 -080032146var noopNgModelController = { $setViewValue: noop, $render: noop };
32147
Ed Tanous4758d5b2017-06-06 15:28:13 -070032148function setOptionSelectedStatus(optionEl, value) {
32149 optionEl.prop('selected', value); // needed for IE
32150 /**
32151 * When unselecting an option, setting the property to null / false should be enough
32152 * However, screenreaders might react to the selected attribute instead, see
32153 * https://github.com/angular/angular.js/issues/14419
32154 * Note: "selected" is a boolean attr and will be removed when the "value" arg in attr() is false
32155 * or null
32156 */
32157 optionEl.attr('selected', value);
Ed Tanous904063f2017-03-02 16:48:24 -080032158}
32159
32160/**
32161 * @ngdoc type
32162 * @name select.SelectController
32163 * @description
32164 * The controller for the `<select>` directive. This provides support for reading
32165 * and writing the selected value(s) of the control and also coordinates dynamically
32166 * added `<option>` elements, perhaps by an `ngRepeat` directive.
32167 */
32168var SelectController =
Ed Tanous4758d5b2017-06-06 15:28:13 -070032169 ['$element', '$scope', /** @this */ function($element, $scope) {
Ed Tanous904063f2017-03-02 16:48:24 -080032170
32171 var self = this,
Ed Tanous4758d5b2017-06-06 15:28:13 -070032172 optionsMap = new NgMap();
32173
32174 self.selectValueMap = {}; // Keys are the hashed values, values the original values
Ed Tanous904063f2017-03-02 16:48:24 -080032175
32176 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
32177 self.ngModelCtrl = noopNgModelController;
Ed Tanous4758d5b2017-06-06 15:28:13 -070032178 self.multiple = false;
Ed Tanous904063f2017-03-02 16:48:24 -080032179
32180 // The "unknown" option is one that is prepended to the list if the viewValue
32181 // does not match any of the options. When it is rendered the value of the unknown
32182 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
32183 //
32184 // We can't just jqLite('<option>') since jqLite is not smart enough
32185 // to create it in <select> and IE barfs otherwise.
32186 self.unknownOption = jqLite(window.document.createElement('option'));
Ed Tanous4758d5b2017-06-06 15:28:13 -070032187
32188 // The empty option is an option with the value '' that te application developer can
32189 // provide inside the select. When the model changes to a value that doesn't match an option,
32190 // it is selected - so if an empty option is provided, no unknown option is generated.
32191 // However, the empty option is not removed when the model matches an option. It is always selectable
32192 // and indicates that a "null" selection has been made.
32193 self.hasEmptyOption = false;
32194 self.emptyOption = undefined;
32195
Ed Tanous904063f2017-03-02 16:48:24 -080032196 self.renderUnknownOption = function(val) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032197 var unknownVal = self.generateUnknownOptionValue(val);
Ed Tanous904063f2017-03-02 16:48:24 -080032198 self.unknownOption.val(unknownVal);
32199 $element.prepend(self.unknownOption);
Ed Tanous4758d5b2017-06-06 15:28:13 -070032200 setOptionSelectedStatus(self.unknownOption, true);
Ed Tanous904063f2017-03-02 16:48:24 -080032201 $element.val(unknownVal);
32202 };
32203
Ed Tanous4758d5b2017-06-06 15:28:13 -070032204 self.updateUnknownOption = function(val) {
32205 var unknownVal = self.generateUnknownOptionValue(val);
32206 self.unknownOption.val(unknownVal);
32207 setOptionSelectedStatus(self.unknownOption, true);
32208 $element.val(unknownVal);
32209 };
32210
32211 self.generateUnknownOptionValue = function(val) {
32212 return '? ' + hashKey(val) + ' ?';
32213 };
32214
32215 self.removeUnknownOption = function() {
32216 if (self.unknownOption.parent()) self.unknownOption.remove();
32217 };
32218
32219 self.selectEmptyOption = function() {
32220 if (self.emptyOption) {
32221 $element.val('');
32222 setOptionSelectedStatus(self.emptyOption, true);
32223 }
32224 };
32225
32226 self.unselectEmptyOption = function() {
32227 if (self.hasEmptyOption) {
32228 self.emptyOption.removeAttr('selected');
32229 }
32230 };
32231
Ed Tanous904063f2017-03-02 16:48:24 -080032232 $scope.$on('$destroy', function() {
32233 // disable unknown option so that we don't do work when the whole select is being destroyed
32234 self.renderUnknownOption = noop;
32235 });
32236
Ed Tanous904063f2017-03-02 16:48:24 -080032237 // Read the value of the select control, the implementation of this changes depending
32238 // upon whether the select can have multiple values and whether ngOptions is at work.
32239 self.readValue = function readSingleValue() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032240 var val = $element.val();
32241 // ngValue added option values are stored in the selectValueMap, normal interpolations are not
32242 var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val;
32243
32244 if (self.hasOption(realVal)) {
32245 return realVal;
32246 }
32247
32248 return null;
Ed Tanous904063f2017-03-02 16:48:24 -080032249 };
32250
32251
32252 // Write the value to the select control, the implementation of this changes depending
32253 // upon whether the select can have multiple values and whether ngOptions is at work.
32254 self.writeValue = function writeSingleValue(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032255 // Make sure to remove the selected attribute from the previously selected option
32256 // Otherwise, screen readers might get confused
32257 var currentlySelectedOption = $element[0].options[$element[0].selectedIndex];
32258 if (currentlySelectedOption) setOptionSelectedStatus(jqLite(currentlySelectedOption), false);
32259
Ed Tanous904063f2017-03-02 16:48:24 -080032260 if (self.hasOption(value)) {
32261 self.removeUnknownOption();
Ed Tanous4758d5b2017-06-06 15:28:13 -070032262
32263 var hashedVal = hashKey(value);
32264 $element.val(hashedVal in self.selectValueMap ? hashedVal : value);
32265
32266 // Set selected attribute and property on selected option for screen readers
32267 var selectedOption = $element[0].options[$element[0].selectedIndex];
32268 setOptionSelectedStatus(jqLite(selectedOption), true);
Ed Tanous904063f2017-03-02 16:48:24 -080032269 } else {
32270 if (value == null && self.emptyOption) {
32271 self.removeUnknownOption();
Ed Tanous4758d5b2017-06-06 15:28:13 -070032272 self.selectEmptyOption();
32273 } else if (self.unknownOption.parent().length) {
32274 self.updateUnknownOption(value);
Ed Tanous904063f2017-03-02 16:48:24 -080032275 } else {
32276 self.renderUnknownOption(value);
32277 }
32278 }
32279 };
32280
32281
32282 // Tell the select control that an option, with the given value, has been added
32283 self.addOption = function(value, element) {
32284 // Skip comment nodes, as they only pollute the `optionsMap`
32285 if (element[0].nodeType === NODE_TYPE_COMMENT) return;
32286
32287 assertNotHasOwnProperty(value, '"option value"');
32288 if (value === '') {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032289 self.hasEmptyOption = true;
Ed Tanous904063f2017-03-02 16:48:24 -080032290 self.emptyOption = element;
32291 }
32292 var count = optionsMap.get(value) || 0;
Ed Tanous4758d5b2017-06-06 15:28:13 -070032293 optionsMap.set(value, count + 1);
32294 // Only render at the end of a digest. This improves render performance when many options
32295 // are added during a digest and ensures all relevant options are correctly marked as selected
32296 scheduleRender();
Ed Tanous904063f2017-03-02 16:48:24 -080032297 };
32298
32299 // Tell the select control that an option, with the given value, has been removed
32300 self.removeOption = function(value) {
32301 var count = optionsMap.get(value);
32302 if (count) {
32303 if (count === 1) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032304 optionsMap.delete(value);
Ed Tanous904063f2017-03-02 16:48:24 -080032305 if (value === '') {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032306 self.hasEmptyOption = false;
Ed Tanous904063f2017-03-02 16:48:24 -080032307 self.emptyOption = undefined;
32308 }
32309 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032310 optionsMap.set(value, count - 1);
Ed Tanous904063f2017-03-02 16:48:24 -080032311 }
32312 }
32313 };
32314
32315 // Check whether the select control has an option matching the given value
32316 self.hasOption = function(value) {
32317 return !!optionsMap.get(value);
32318 };
32319
32320
Ed Tanous4758d5b2017-06-06 15:28:13 -070032321 var renderScheduled = false;
32322 function scheduleRender() {
32323 if (renderScheduled) return;
32324 renderScheduled = true;
32325 $scope.$$postDigest(function() {
32326 renderScheduled = false;
32327 self.ngModelCtrl.$render();
32328 });
32329 }
32330
32331 var updateScheduled = false;
32332 function scheduleViewValueUpdate(renderAfter) {
32333 if (updateScheduled) return;
32334
32335 updateScheduled = true;
32336
32337 $scope.$$postDigest(function() {
32338 if ($scope.$$destroyed) return;
32339
32340 updateScheduled = false;
32341 self.ngModelCtrl.$setViewValue(self.readValue());
32342 if (renderAfter) self.ngModelCtrl.$render();
32343 });
32344 }
32345
32346
Ed Tanous904063f2017-03-02 16:48:24 -080032347 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
32348
Ed Tanous4758d5b2017-06-06 15:28:13 -070032349 if (optionAttrs.$attr.ngValue) {
32350 // The value attribute is set by ngValue
32351 var oldVal, hashedVal = NaN;
Ed Tanous904063f2017-03-02 16:48:24 -080032352 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032353
32354 var removal;
32355 var previouslySelected = optionElement.prop('selected');
32356
32357 if (isDefined(hashedVal)) {
32358 self.removeOption(oldVal);
32359 delete self.selectValueMap[hashedVal];
32360 removal = true;
32361 }
32362
32363 hashedVal = hashKey(newVal);
32364 oldVal = newVal;
32365 self.selectValueMap[hashedVal] = newVal;
32366 self.addOption(newVal, optionElement);
32367 // Set the attribute directly instead of using optionAttrs.$set - this stops the observer
32368 // from firing a second time. Other $observers on value will also get the result of the
32369 // ngValue expression, not the hashed value
32370 optionElement.attr('value', hashedVal);
32371
32372 if (removal && previouslySelected) {
32373 scheduleViewValueUpdate();
32374 }
32375
32376 });
32377 } else if (interpolateValueFn) {
32378 // The value attribute is interpolated
32379 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
32380 // This method is overwritten in ngOptions and has side-effects!
32381 self.readValue();
32382
32383 var removal;
32384 var previouslySelected = optionElement.prop('selected');
32385
Ed Tanous904063f2017-03-02 16:48:24 -080032386 if (isDefined(oldVal)) {
32387 self.removeOption(oldVal);
Ed Tanous4758d5b2017-06-06 15:28:13 -070032388 removal = true;
Ed Tanous904063f2017-03-02 16:48:24 -080032389 }
32390 oldVal = newVal;
32391 self.addOption(newVal, optionElement);
Ed Tanous4758d5b2017-06-06 15:28:13 -070032392
32393 if (removal && previouslySelected) {
32394 scheduleViewValueUpdate();
32395 }
Ed Tanous904063f2017-03-02 16:48:24 -080032396 });
32397 } else if (interpolateTextFn) {
32398 // The text content is interpolated
32399 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
32400 optionAttrs.$set('value', newVal);
Ed Tanous4758d5b2017-06-06 15:28:13 -070032401 var previouslySelected = optionElement.prop('selected');
Ed Tanous904063f2017-03-02 16:48:24 -080032402 if (oldVal !== newVal) {
32403 self.removeOption(oldVal);
32404 }
32405 self.addOption(newVal, optionElement);
Ed Tanous4758d5b2017-06-06 15:28:13 -070032406
32407 if (oldVal && previouslySelected) {
32408 scheduleViewValueUpdate();
32409 }
Ed Tanous904063f2017-03-02 16:48:24 -080032410 });
32411 } else {
32412 // The value attribute is static
32413 self.addOption(optionAttrs.value, optionElement);
32414 }
32415
Ed Tanous4758d5b2017-06-06 15:28:13 -070032416
32417 optionAttrs.$observe('disabled', function(newVal) {
32418
32419 // Since model updates will also select disabled options (like ngOptions),
32420 // we only have to handle options becoming disabled, not enabled
32421
32422 if (newVal === 'true' || newVal && optionElement.prop('selected')) {
32423 if (self.multiple) {
32424 scheduleViewValueUpdate(true);
32425 } else {
32426 self.ngModelCtrl.$setViewValue(null);
32427 self.ngModelCtrl.$render();
32428 }
32429 }
32430 });
32431
Ed Tanous904063f2017-03-02 16:48:24 -080032432 optionElement.on('$destroy', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032433 var currentValue = self.readValue();
32434 var removeValue = optionAttrs.value;
32435
32436 self.removeOption(removeValue);
32437 scheduleRender();
32438
32439 if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 ||
32440 currentValue === removeValue
32441 ) {
32442 // When multiple (selected) options are destroyed at the same time, we don't want
32443 // to run a model update for each of them. Instead, run a single update in the $$postDigest
32444 scheduleViewValueUpdate(true);
32445 }
Ed Tanous904063f2017-03-02 16:48:24 -080032446 });
32447 };
32448}];
32449
32450/**
32451 * @ngdoc directive
32452 * @name select
32453 * @restrict E
32454 *
32455 * @description
Ed Tanous4758d5b2017-06-06 15:28:13 -070032456 * HTML `select` element with angular data-binding.
Ed Tanous904063f2017-03-02 16:48:24 -080032457 *
32458 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
32459 * between the scope and the `<select>` control (including setting default values).
32460 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
32461 * {@link ngOptions `ngOptions`} directives.
32462 *
32463 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
32464 * to the model identified by the `ngModel` directive. With static or repeated options, this is
32465 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
Ed Tanous4758d5b2017-06-06 15:28:13 -070032466 * Value and textContent can be interpolated.
Ed Tanous904063f2017-03-02 16:48:24 -080032467 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070032468 * ## Matching model and option values
32469 *
32470 * In general, the match between the model and an option is evaluated by strictly comparing the model
32471 * value against the value of the available options.
32472 *
32473 * If you are setting the option value with the option's `value` attribute, or textContent, the
32474 * value will always be a `string` which means that the model value must also be a string.
32475 * Otherwise the `select` directive cannot match them correctly.
32476 *
32477 * To bind the model to a non-string value, you can use one of the following strategies:
32478 * - the {@link ng.ngOptions `ngOptions`} directive
32479 * ({@link ng.select#using-select-with-ngoptions-and-setting-a-default-value})
32480 * - the {@link ng.ngValue `ngValue`} directive, which allows arbitrary expressions to be
32481 * option values ({@link ng.select#using-ngvalue-to-bind-the-model-to-an-array-of-objects Example})
32482 * - model $parsers / $formatters to convert the string value
32483 * ({@link ng.select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting Example})
Ed Tanous904063f2017-03-02 16:48:24 -080032484 *
32485 * If the viewValue of `ngModel` does not match any of the options, then the control
32486 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
32487 *
32488 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
32489 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
32490 * option. See example below for demonstration.
32491 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070032492 * ## Choosing between `ngRepeat` and `ngOptions`
32493 *
Ed Tanous904063f2017-03-02 16:48:24 -080032494 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
Ed Tanous4758d5b2017-06-06 15:28:13 -070032495 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits:
32496 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
32497 * comprehension expression
32498 * - reduced memory consumption by not creating a new scope for each repeated instance
32499 * - increased render speed by creating the options in a documentFragment instead of individually
32500 *
32501 * Specifically, select with repeated options slows down significantly starting at 2000 options in
32502 * Chrome and Internet Explorer / Edge.
Ed Tanous904063f2017-03-02 16:48:24 -080032503 *
32504 *
32505 * @param {string} ngModel Assignable angular expression to data-bind to.
32506 * @param {string=} name Property name of the form under which the control is published.
32507 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
32508 * bound to the model as an array.
32509 * @param {string=} required Sets `required` validation error key if the value is not entered.
32510 * @param {string=} ngRequired Adds required attribute and required validation constraint to
32511 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
32512 * when you want to data-bind to the required attribute.
32513 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
32514 * interaction with the select element.
32515 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
32516 * set on the model on selection. See {@link ngOptions `ngOptions`}.
Ed Tanous4758d5b2017-06-06 15:28:13 -070032517 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
32518 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
Ed Tanous904063f2017-03-02 16:48:24 -080032519 *
32520 * @example
32521 * ### Simple `select` elements with static options
32522 *
32523 * <example name="static-select" module="staticSelect">
32524 * <file name="index.html">
32525 * <div ng-controller="ExampleController">
32526 * <form name="myForm">
32527 * <label for="singleSelect"> Single select: </label><br>
32528 * <select name="singleSelect" ng-model="data.singleSelect">
32529 * <option value="option-1">Option 1</option>
32530 * <option value="option-2">Option 2</option>
32531 * </select><br>
32532 *
32533 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
32534 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
32535 * <option value="">---Please select---</option> <!-- not selected / blank option -->
32536 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
32537 * <option value="option-2">Option 2</option>
32538 * </select><br>
32539 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
32540 * <tt>singleSelect = {{data.singleSelect}}</tt>
32541 *
32542 * <hr>
32543 * <label for="multipleSelect"> Multiple select: </label><br>
32544 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
32545 * <option value="option-1">Option 1</option>
32546 * <option value="option-2">Option 2</option>
32547 * <option value="option-3">Option 3</option>
32548 * </select><br>
32549 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
32550 * </form>
32551 * </div>
32552 * </file>
32553 * <file name="app.js">
32554 * angular.module('staticSelect', [])
32555 * .controller('ExampleController', ['$scope', function($scope) {
32556 * $scope.data = {
32557 * singleSelect: null,
32558 * multipleSelect: [],
Ed Tanous4758d5b2017-06-06 15:28:13 -070032559 * option1: 'option-1'
Ed Tanous904063f2017-03-02 16:48:24 -080032560 * };
32561 *
32562 * $scope.forceUnknownOption = function() {
32563 * $scope.data.singleSelect = 'nonsense';
32564 * };
32565 * }]);
32566 * </file>
32567 *</example>
32568 *
32569 * ### Using `ngRepeat` to generate `select` options
Ed Tanous4758d5b2017-06-06 15:28:13 -070032570 * <example name="select-ngrepeat" module="ngrepeatSelect">
Ed Tanous904063f2017-03-02 16:48:24 -080032571 * <file name="index.html">
32572 * <div ng-controller="ExampleController">
32573 * <form name="myForm">
32574 * <label for="repeatSelect"> Repeat select: </label>
Ed Tanous4758d5b2017-06-06 15:28:13 -070032575 * <select name="repeatSelect" id="repeatSelect" ng-model="data.model">
Ed Tanous904063f2017-03-02 16:48:24 -080032576 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
32577 * </select>
32578 * </form>
32579 * <hr>
Ed Tanous4758d5b2017-06-06 15:28:13 -070032580 * <tt>model = {{data.model}}</tt><br/>
Ed Tanous904063f2017-03-02 16:48:24 -080032581 * </div>
32582 * </file>
32583 * <file name="app.js">
32584 * angular.module('ngrepeatSelect', [])
32585 * .controller('ExampleController', ['$scope', function($scope) {
32586 * $scope.data = {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032587 * model: null,
Ed Tanous904063f2017-03-02 16:48:24 -080032588 * availableOptions: [
32589 * {id: '1', name: 'Option A'},
32590 * {id: '2', name: 'Option B'},
32591 * {id: '3', name: 'Option C'}
Ed Tanous4758d5b2017-06-06 15:28:13 -070032592 * ]
Ed Tanous904063f2017-03-02 16:48:24 -080032593 * };
32594 * }]);
32595 * </file>
32596 *</example>
32597 *
Ed Tanous4758d5b2017-06-06 15:28:13 -070032598 * ### Using `ngValue` to bind the model to an array of objects
32599 * <example name="select-ngvalue" module="ngvalueSelect">
32600 * <file name="index.html">
32601 * <div ng-controller="ExampleController">
32602 * <form name="myForm">
32603 * <label for="ngvalueselect"> ngvalue select: </label>
32604 * <select size="6" name="ngvalueselect" ng-model="data.model" multiple>
32605 * <option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option>
32606 * </select>
32607 * </form>
32608 * <hr>
32609 * <pre>model = {{data.model | json}}</pre><br/>
32610 * </div>
32611 * </file>
32612 * <file name="app.js">
32613 * angular.module('ngvalueSelect', [])
32614 * .controller('ExampleController', ['$scope', function($scope) {
32615 * $scope.data = {
32616 * model: null,
32617 * availableOptions: [
32618 {value: 'myString', name: 'string'},
32619 {value: 1, name: 'integer'},
32620 {value: true, name: 'boolean'},
32621 {value: null, name: 'null'},
32622 {value: {prop: 'value'}, name: 'object'},
32623 {value: ['a'], name: 'array'}
32624 * ]
32625 * };
32626 * }]);
32627 * </file>
32628 *</example>
Ed Tanous904063f2017-03-02 16:48:24 -080032629 *
32630 * ### Using `select` with `ngOptions` and setting a default value
32631 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
32632 *
32633 * <example name="select-with-default-values" module="defaultValueSelect">
32634 * <file name="index.html">
32635 * <div ng-controller="ExampleController">
32636 * <form name="myForm">
32637 * <label for="mySelect">Make a choice:</label>
32638 * <select name="mySelect" id="mySelect"
32639 * ng-options="option.name for option in data.availableOptions track by option.id"
32640 * ng-model="data.selectedOption"></select>
32641 * </form>
32642 * <hr>
32643 * <tt>option = {{data.selectedOption}}</tt><br/>
32644 * </div>
32645 * </file>
32646 * <file name="app.js">
32647 * angular.module('defaultValueSelect', [])
32648 * .controller('ExampleController', ['$scope', function($scope) {
32649 * $scope.data = {
32650 * availableOptions: [
32651 * {id: '1', name: 'Option A'},
32652 * {id: '2', name: 'Option B'},
32653 * {id: '3', name: 'Option C'}
32654 * ],
32655 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
32656 * };
32657 * }]);
32658 * </file>
32659 *</example>
32660 *
32661 *
32662 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
32663 *
32664 * <example name="select-with-non-string-options" module="nonStringSelect">
32665 * <file name="index.html">
32666 * <select ng-model="model.id" convert-to-number>
32667 * <option value="0">Zero</option>
32668 * <option value="1">One</option>
32669 * <option value="2">Two</option>
32670 * </select>
32671 * {{ model }}
32672 * </file>
32673 * <file name="app.js">
32674 * angular.module('nonStringSelect', [])
32675 * .run(function($rootScope) {
32676 * $rootScope.model = { id: 2 };
32677 * })
32678 * .directive('convertToNumber', function() {
32679 * return {
32680 * require: 'ngModel',
32681 * link: function(scope, element, attrs, ngModel) {
32682 * ngModel.$parsers.push(function(val) {
32683 * return parseInt(val, 10);
32684 * });
32685 * ngModel.$formatters.push(function(val) {
32686 * return '' + val;
32687 * });
32688 * }
32689 * };
32690 * });
32691 * </file>
32692 * <file name="protractor.js" type="protractor">
32693 * it('should initialize to model', function() {
Ed Tanous904063f2017-03-02 16:48:24 -080032694 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
32695 * });
32696 * </file>
32697 * </example>
32698 *
32699 */
32700var selectDirective = function() {
32701
32702 return {
32703 restrict: 'E',
32704 require: ['select', '?ngModel'],
32705 controller: SelectController,
32706 priority: 1,
32707 link: {
32708 pre: selectPreLink,
32709 post: selectPostLink
32710 }
32711 };
32712
32713 function selectPreLink(scope, element, attr, ctrls) {
32714
Ed Tanous904063f2017-03-02 16:48:24 -080032715 var selectCtrl = ctrls[0];
Ed Tanous4758d5b2017-06-06 15:28:13 -070032716 var ngModelCtrl = ctrls[1];
32717
32718 // if ngModel is not defined, we don't need to do anything but set the registerOption
32719 // function to noop, so options don't get added internally
32720 if (!ngModelCtrl) {
32721 selectCtrl.registerOption = noop;
32722 return;
32723 }
32724
Ed Tanous904063f2017-03-02 16:48:24 -080032725
32726 selectCtrl.ngModelCtrl = ngModelCtrl;
32727
32728 // When the selected item(s) changes we delegate getting the value of the select control
32729 // to the `readValue` method, which can be changed if the select can have multiple
32730 // selected values or if the options are being generated by `ngOptions`
32731 element.on('change', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032732 selectCtrl.removeUnknownOption();
Ed Tanous904063f2017-03-02 16:48:24 -080032733 scope.$apply(function() {
32734 ngModelCtrl.$setViewValue(selectCtrl.readValue());
32735 });
32736 });
32737
32738 // If the select allows multiple values then we need to modify how we read and write
32739 // values from and to the control; also what it means for the value to be empty and
32740 // we have to add an extra watch since ngModel doesn't work well with arrays - it
32741 // doesn't trigger rendering if only an item in the array changes.
32742 if (attr.multiple) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032743 selectCtrl.multiple = true;
Ed Tanous904063f2017-03-02 16:48:24 -080032744
32745 // Read value now needs to check each option to see if it is selected
32746 selectCtrl.readValue = function readMultipleValue() {
32747 var array = [];
32748 forEach(element.find('option'), function(option) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032749 if (option.selected && !option.disabled) {
32750 var val = option.value;
32751 array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val);
Ed Tanous904063f2017-03-02 16:48:24 -080032752 }
32753 });
32754 return array;
32755 };
32756
32757 // Write value now needs to set the selected property of each matching option
32758 selectCtrl.writeValue = function writeMultipleValue(value) {
Ed Tanous904063f2017-03-02 16:48:24 -080032759 forEach(element.find('option'), function(option) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032760 var shouldBeSelected = !!value && (includes(value, option.value) ||
32761 includes(value, selectCtrl.selectValueMap[option.value]));
32762 var currentlySelected = option.selected;
32763
32764 // IE and Edge, adding options to the selection via shift+click/UP/DOWN,
32765 // will de-select already selected options if "selected" on those options was set
32766 // more than once (i.e. when the options were already selected)
32767 // So we only modify the selected property if neccessary.
32768 // Note: this behavior cannot be replicated via unit tests because it only shows in the
32769 // actual user interface.
32770 if (shouldBeSelected !== currentlySelected) {
32771 setOptionSelectedStatus(jqLite(option), shouldBeSelected);
32772 }
32773
Ed Tanous904063f2017-03-02 16:48:24 -080032774 });
32775 };
32776
32777 // we have to do it on each watch since ngModel watches reference, but
32778 // we need to work of an array, so we need to see if anything was inserted/removed
32779 var lastView, lastViewRef = NaN;
32780 scope.$watch(function selectMultipleWatch() {
32781 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
32782 lastView = shallowCopy(ngModelCtrl.$viewValue);
32783 ngModelCtrl.$render();
32784 }
32785 lastViewRef = ngModelCtrl.$viewValue;
32786 });
32787
32788 // If we are a multiple select then value is now a collection
32789 // so the meaning of $isEmpty changes
32790 ngModelCtrl.$isEmpty = function(value) {
32791 return !value || value.length === 0;
32792 };
32793
32794 }
32795 }
32796
32797 function selectPostLink(scope, element, attrs, ctrls) {
32798 // if ngModel is not defined, we don't need to do anything
32799 var ngModelCtrl = ctrls[1];
32800 if (!ngModelCtrl) return;
32801
32802 var selectCtrl = ctrls[0];
32803
32804 // We delegate rendering to the `writeValue` method, which can be changed
32805 // if the select can have multiple selected values or if the options are being
32806 // generated by `ngOptions`.
32807 // This must be done in the postLink fn to prevent $render to be called before
32808 // all nodes have been linked correctly.
32809 ngModelCtrl.$render = function() {
32810 selectCtrl.writeValue(ngModelCtrl.$viewValue);
32811 };
32812 }
32813};
32814
32815
32816// The option directive is purely designed to communicate the existence (or lack of)
32817// of dynamically created (and destroyed) option elements to their containing select
32818// directive via its controller.
32819var optionDirective = ['$interpolate', function($interpolate) {
32820 return {
32821 restrict: 'E',
32822 priority: 100,
32823 compile: function(element, attr) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070032824 var interpolateValueFn, interpolateTextFn;
32825
32826 if (isDefined(attr.ngValue)) {
32827 // Will be handled by registerOption
32828 } else if (isDefined(attr.value)) {
Ed Tanous904063f2017-03-02 16:48:24 -080032829 // If the value attribute is defined, check if it contains an interpolation
Ed Tanous4758d5b2017-06-06 15:28:13 -070032830 interpolateValueFn = $interpolate(attr.value, true);
Ed Tanous904063f2017-03-02 16:48:24 -080032831 } else {
32832 // If the value attribute is not defined then we fall back to the
32833 // text content of the option element, which may be interpolated
Ed Tanous4758d5b2017-06-06 15:28:13 -070032834 interpolateTextFn = $interpolate(element.text(), true);
Ed Tanous904063f2017-03-02 16:48:24 -080032835 if (!interpolateTextFn) {
32836 attr.$set('value', element.text());
32837 }
32838 }
32839
32840 return function(scope, element, attr) {
32841 // This is an optimization over using ^^ since we don't want to have to search
32842 // all the way to the root of the DOM for every single option element
32843 var selectCtrlName = '$selectController',
32844 parent = element.parent(),
32845 selectCtrl = parent.data(selectCtrlName) ||
32846 parent.parent().data(selectCtrlName); // in case we are in optgroup
32847
32848 if (selectCtrl) {
32849 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
32850 }
32851 };
32852 }
32853 };
32854}];
32855
Ed Tanous904063f2017-03-02 16:48:24 -080032856/**
32857 * @ngdoc directive
32858 * @name ngRequired
32859 * @restrict A
32860 *
32861 * @description
32862 *
32863 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
32864 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be
32865 * applied to custom controls.
32866 *
32867 * The directive sets the `required` attribute on the element if the Angular expression inside
32868 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we
32869 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide}
32870 * for more info.
32871 *
32872 * The validator will set the `required` error key to true if the `required` attribute is set and
32873 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the
32874 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the
32875 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
32876 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based.
32877 *
32878 * @example
32879 * <example name="ngRequiredDirective" module="ngRequiredExample">
32880 * <file name="index.html">
32881 * <script>
32882 * angular.module('ngRequiredExample', [])
32883 * .controller('ExampleController', ['$scope', function($scope) {
32884 * $scope.required = true;
32885 * }]);
32886 * </script>
32887 * <div ng-controller="ExampleController">
32888 * <form name="form">
32889 * <label for="required">Toggle required: </label>
32890 * <input type="checkbox" ng-model="required" id="required" />
32891 * <br>
32892 * <label for="input">This input must be filled if `required` is true: </label>
32893 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br>
32894 * <hr>
32895 * required error set? = <code>{{form.input.$error.required}}</code><br>
32896 * model = <code>{{model}}</code>
32897 * </form>
32898 * </div>
32899 * </file>
32900 * <file name="protractor.js" type="protractor">
32901 var required = element(by.binding('form.input.$error.required'));
32902 var model = element(by.binding('model'));
32903 var input = element(by.id('input'));
32904
32905 it('should set the required error', function() {
32906 expect(required.getText()).toContain('true');
32907
32908 input.sendKeys('123');
32909 expect(required.getText()).not.toContain('true');
32910 expect(model.getText()).toContain('123');
32911 });
32912 * </file>
32913 * </example>
32914 */
32915var requiredDirective = function() {
32916 return {
32917 restrict: 'A',
32918 require: '?ngModel',
32919 link: function(scope, elm, attr, ctrl) {
32920 if (!ctrl) return;
32921 attr.required = true; // force truthy in case we are on non input element
32922
32923 ctrl.$validators.required = function(modelValue, viewValue) {
32924 return !attr.required || !ctrl.$isEmpty(viewValue);
32925 };
32926
32927 attr.$observe('required', function() {
32928 ctrl.$validate();
32929 });
32930 }
32931 };
32932};
32933
32934/**
32935 * @ngdoc directive
32936 * @name ngPattern
32937 *
32938 * @description
32939 *
32940 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
32941 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
32942 *
32943 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
32944 * does not match a RegExp which is obtained by evaluating the Angular expression given in the
32945 * `ngPattern` attribute value:
32946 * * If the expression evaluates to a RegExp object, then this is used directly.
32947 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it
32948 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
32949 *
32950 * <div class="alert alert-info">
32951 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
32952 * start at the index of the last search's match, thus not taking the whole input value into
32953 * account.
32954 * </div>
32955 *
32956 * <div class="alert alert-info">
32957 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two
32958 * differences:
32959 * <ol>
32960 * <li>
32961 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is
32962 * not available.
32963 * </li>
32964 * <li>
32965 * The `ngPattern` attribute must be an expression, while the `pattern` value must be
32966 * interpolated.
32967 * </li>
32968 * </ol>
32969 * </div>
32970 *
32971 * @example
32972 * <example name="ngPatternDirective" module="ngPatternExample">
32973 * <file name="index.html">
32974 * <script>
32975 * angular.module('ngPatternExample', [])
32976 * .controller('ExampleController', ['$scope', function($scope) {
32977 * $scope.regex = '\\d+';
32978 * }]);
32979 * </script>
32980 * <div ng-controller="ExampleController">
32981 * <form name="form">
32982 * <label for="regex">Set a pattern (regex string): </label>
32983 * <input type="text" ng-model="regex" id="regex" />
32984 * <br>
32985 * <label for="input">This input is restricted by the current pattern: </label>
32986 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br>
32987 * <hr>
32988 * input valid? = <code>{{form.input.$valid}}</code><br>
32989 * model = <code>{{model}}</code>
32990 * </form>
32991 * </div>
32992 * </file>
32993 * <file name="protractor.js" type="protractor">
32994 var model = element(by.binding('model'));
32995 var input = element(by.id('input'));
32996
32997 it('should validate the input with the default pattern', function() {
32998 input.sendKeys('aaa');
32999 expect(model.getText()).not.toContain('aaa');
33000
33001 input.clear().then(function() {
33002 input.sendKeys('123');
33003 expect(model.getText()).toContain('123');
33004 });
33005 });
33006 * </file>
33007 * </example>
33008 */
33009var patternDirective = function() {
33010 return {
33011 restrict: 'A',
33012 require: '?ngModel',
33013 link: function(scope, elm, attr, ctrl) {
33014 if (!ctrl) return;
33015
33016 var regexp, patternExp = attr.ngPattern || attr.pattern;
33017 attr.$observe('pattern', function(regex) {
33018 if (isString(regex) && regex.length > 0) {
33019 regex = new RegExp('^' + regex + '$');
33020 }
33021
33022 if (regex && !regex.test) {
33023 throw minErr('ngPattern')('noregexp',
33024 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
33025 regex, startingTag(elm));
33026 }
33027
33028 regexp = regex || undefined;
33029 ctrl.$validate();
33030 });
33031
33032 ctrl.$validators.pattern = function(modelValue, viewValue) {
33033 // HTML5 pattern constraint validates the input value, so we validate the viewValue
33034 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
33035 };
33036 }
33037 };
33038};
33039
33040/**
33041 * @ngdoc directive
33042 * @name ngMaxlength
33043 *
33044 * @description
33045 *
33046 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
33047 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
33048 *
33049 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
33050 * is longer than the integer obtained by evaluating the Angular expression given in the
33051 * `ngMaxlength` attribute value.
33052 *
33053 * <div class="alert alert-info">
33054 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two
33055 * differences:
33056 * <ol>
33057 * <li>
33058 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint
33059 * validation is not available.
33060 * </li>
33061 * <li>
33062 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be
33063 * interpolated.
33064 * </li>
33065 * </ol>
33066 * </div>
33067 *
33068 * @example
33069 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample">
33070 * <file name="index.html">
33071 * <script>
33072 * angular.module('ngMaxlengthExample', [])
33073 * .controller('ExampleController', ['$scope', function($scope) {
33074 * $scope.maxlength = 5;
33075 * }]);
33076 * </script>
33077 * <div ng-controller="ExampleController">
33078 * <form name="form">
33079 * <label for="maxlength">Set a maxlength: </label>
33080 * <input type="number" ng-model="maxlength" id="maxlength" />
33081 * <br>
33082 * <label for="input">This input is restricted by the current maxlength: </label>
33083 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br>
33084 * <hr>
33085 * input valid? = <code>{{form.input.$valid}}</code><br>
33086 * model = <code>{{model}}</code>
33087 * </form>
33088 * </div>
33089 * </file>
33090 * <file name="protractor.js" type="protractor">
33091 var model = element(by.binding('model'));
33092 var input = element(by.id('input'));
33093
33094 it('should validate the input with the default maxlength', function() {
33095 input.sendKeys('abcdef');
33096 expect(model.getText()).not.toContain('abcdef');
33097
33098 input.clear().then(function() {
33099 input.sendKeys('abcde');
33100 expect(model.getText()).toContain('abcde');
33101 });
33102 });
33103 * </file>
33104 * </example>
33105 */
33106var maxlengthDirective = function() {
33107 return {
33108 restrict: 'A',
33109 require: '?ngModel',
33110 link: function(scope, elm, attr, ctrl) {
33111 if (!ctrl) return;
33112
33113 var maxlength = -1;
33114 attr.$observe('maxlength', function(value) {
33115 var intVal = toInt(value);
Ed Tanous4758d5b2017-06-06 15:28:13 -070033116 maxlength = isNumberNaN(intVal) ? -1 : intVal;
Ed Tanous904063f2017-03-02 16:48:24 -080033117 ctrl.$validate();
33118 });
33119 ctrl.$validators.maxlength = function(modelValue, viewValue) {
33120 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
33121 };
33122 }
33123 };
33124};
33125
33126/**
33127 * @ngdoc directive
33128 * @name ngMinlength
33129 *
33130 * @description
33131 *
33132 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
33133 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
33134 *
33135 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
33136 * is shorter than the integer obtained by evaluating the Angular expression given in the
33137 * `ngMinlength` attribute value.
33138 *
33139 * <div class="alert alert-info">
33140 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two
33141 * differences:
33142 * <ol>
33143 * <li>
33144 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint
33145 * validation is not available.
33146 * </li>
33147 * <li>
33148 * The `ngMinlength` value must be an expression, while the `minlength` value must be
33149 * interpolated.
33150 * </li>
33151 * </ol>
33152 * </div>
33153 *
33154 * @example
33155 * <example name="ngMinlengthDirective" module="ngMinlengthExample">
33156 * <file name="index.html">
33157 * <script>
33158 * angular.module('ngMinlengthExample', [])
33159 * .controller('ExampleController', ['$scope', function($scope) {
33160 * $scope.minlength = 3;
33161 * }]);
33162 * </script>
33163 * <div ng-controller="ExampleController">
33164 * <form name="form">
33165 * <label for="minlength">Set a minlength: </label>
33166 * <input type="number" ng-model="minlength" id="minlength" />
33167 * <br>
33168 * <label for="input">This input is restricted by the current minlength: </label>
33169 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br>
33170 * <hr>
33171 * input valid? = <code>{{form.input.$valid}}</code><br>
33172 * model = <code>{{model}}</code>
33173 * </form>
33174 * </div>
33175 * </file>
33176 * <file name="protractor.js" type="protractor">
33177 var model = element(by.binding('model'));
33178 var input = element(by.id('input'));
33179
33180 it('should validate the input with the default minlength', function() {
33181 input.sendKeys('ab');
33182 expect(model.getText()).not.toContain('ab');
33183
33184 input.sendKeys('abc');
33185 expect(model.getText()).toContain('abc');
33186 });
33187 * </file>
33188 * </example>
33189 */
33190var minlengthDirective = function() {
33191 return {
33192 restrict: 'A',
33193 require: '?ngModel',
33194 link: function(scope, elm, attr, ctrl) {
33195 if (!ctrl) return;
33196
33197 var minlength = 0;
33198 attr.$observe('minlength', function(value) {
33199 minlength = toInt(value) || 0;
33200 ctrl.$validate();
33201 });
33202 ctrl.$validators.minlength = function(modelValue, viewValue) {
33203 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
33204 };
33205 }
33206 };
33207};
33208
33209if (window.angular.bootstrap) {
Ed Tanous4758d5b2017-06-06 15:28:13 -070033210 // AngularJS is already loaded, so we can return here...
Ed Tanous904063f2017-03-02 16:48:24 -080033211 if (window.console) {
33212 console.log('WARNING: Tried to load angular more than once.');
33213 }
33214 return;
33215}
33216
Ed Tanous4758d5b2017-06-06 15:28:13 -070033217// try to bind to jquery now so that one can write jqLite(fn)
33218// but we will rebind on bootstrap again.
Ed Tanous904063f2017-03-02 16:48:24 -080033219bindJQuery();
33220
33221publishExternalAPI(angular);
33222
33223angular.module("ngLocale", [], ["$provide", function($provide) {
33224var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
33225function getDecimals(n) {
33226 n = n + '';
33227 var i = n.indexOf('.');
33228 return (i == -1) ? 0 : n.length - i - 1;
33229}
33230
33231function getVF(n, opt_precision) {
33232 var v = opt_precision;
33233
33234 if (undefined === v) {
33235 v = Math.min(getDecimals(n), 3);
33236 }
33237
33238 var base = Math.pow(10, v);
33239 var f = ((n * base) | 0) % base;
33240 return {v: v, f: f};
33241}
33242
33243$provide.value("$locale", {
33244 "DATETIME_FORMATS": {
33245 "AMPMS": [
33246 "AM",
33247 "PM"
33248 ],
33249 "DAY": [
33250 "Sunday",
33251 "Monday",
33252 "Tuesday",
33253 "Wednesday",
33254 "Thursday",
33255 "Friday",
33256 "Saturday"
33257 ],
33258 "ERANAMES": [
33259 "Before Christ",
33260 "Anno Domini"
33261 ],
33262 "ERAS": [
33263 "BC",
33264 "AD"
33265 ],
33266 "FIRSTDAYOFWEEK": 6,
33267 "MONTH": [
33268 "January",
33269 "February",
33270 "March",
33271 "April",
33272 "May",
33273 "June",
33274 "July",
33275 "August",
33276 "September",
33277 "October",
33278 "November",
33279 "December"
33280 ],
33281 "SHORTDAY": [
33282 "Sun",
33283 "Mon",
33284 "Tue",
33285 "Wed",
33286 "Thu",
33287 "Fri",
33288 "Sat"
33289 ],
33290 "SHORTMONTH": [
33291 "Jan",
33292 "Feb",
33293 "Mar",
33294 "Apr",
33295 "May",
33296 "Jun",
33297 "Jul",
33298 "Aug",
33299 "Sep",
33300 "Oct",
33301 "Nov",
33302 "Dec"
33303 ],
33304 "STANDALONEMONTH": [
33305 "January",
33306 "February",
33307 "March",
33308 "April",
33309 "May",
33310 "June",
33311 "July",
33312 "August",
33313 "September",
33314 "October",
33315 "November",
33316 "December"
33317 ],
33318 "WEEKENDRANGE": [
33319 5,
33320 6
33321 ],
33322 "fullDate": "EEEE, MMMM d, y",
33323 "longDate": "MMMM d, y",
33324 "medium": "MMM d, y h:mm:ss a",
33325 "mediumDate": "MMM d, y",
33326 "mediumTime": "h:mm:ss a",
33327 "short": "M/d/yy h:mm a",
33328 "shortDate": "M/d/yy",
33329 "shortTime": "h:mm a"
33330 },
33331 "NUMBER_FORMATS": {
33332 "CURRENCY_SYM": "$",
33333 "DECIMAL_SEP": ".",
33334 "GROUP_SEP": ",",
33335 "PATTERNS": [
33336 {
33337 "gSize": 3,
33338 "lgSize": 3,
33339 "maxFrac": 3,
33340 "minFrac": 0,
33341 "minInt": 1,
33342 "negPre": "-",
33343 "negSuf": "",
33344 "posPre": "",
33345 "posSuf": ""
33346 },
33347 {
33348 "gSize": 3,
33349 "lgSize": 3,
33350 "maxFrac": 2,
33351 "minFrac": 2,
33352 "minInt": 1,
33353 "negPre": "-\u00a4",
33354 "negSuf": "",
33355 "posPre": "\u00a4",
33356 "posSuf": ""
33357 }
33358 ]
33359 },
33360 "id": "en-us",
33361 "localeID": "en_US",
33362 "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;}
33363});
33364}]);
33365
Ed Tanous4758d5b2017-06-06 15:28:13 -070033366 jqLite(function() {
Ed Tanous904063f2017-03-02 16:48:24 -080033367 angularInit(window.document, bootstrap);
33368 });
33369
33370})(window);
33371
33372!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@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;}</style>');