blob: a3aee7d6a51ab08914cad6f7a85f102d372924ce [file] [log] [blame]
Matteo Scandolo7cd88ba2015-12-16 14:23:08 -08001/**
2 * @license AngularJS v1.4.8
3 * (c) 2010-2015 Google, Inc. http://angularjs.org
4 * License: MIT
5 */
6(function(window, document, undefined) {'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() {
41 var SKIP_INDEXES = 2;
42
43 var templateArgs = arguments,
44 code = templateArgs[0],
45 message = '[' + (module ? module + ':' : '') + code + '] ',
46 template = templateArgs[1],
47 paramPrefix, i;
48
49 message += template.replace(/\{\d+\}/g, function(match) {
50 var index = +match.slice(1, -1),
51 shiftedIndex = index + SKIP_INDEXES;
52
53 if (shiftedIndex < templateArgs.length) {
54 return toDebugString(templateArgs[shiftedIndex]);
55 }
56
57 return match;
58 });
59
60 message += '\nhttp://errors.angularjs.org/1.4.8/' +
61 (module ? module + '/' : '') + code;
62
63 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
64 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
65 encodeURIComponent(toDebugString(templateArgs[i]));
66 }
67
68 return new ErrorConstructor(message);
69 };
70}
71
72/* We need to tell jshint what variables are being exported */
73/* global angular: true,
74 msie: true,
75 jqLite: true,
76 jQuery: true,
77 slice: true,
78 splice: true,
79 push: true,
80 toString: true,
81 ngMinErr: true,
82 angularModule: true,
83 uid: true,
84 REGEX_STRING_REGEXP: true,
85 VALIDITY_STATE_PROPERTY: true,
86
87 lowercase: true,
88 uppercase: true,
89 manualLowercase: true,
90 manualUppercase: true,
91 nodeName_: true,
92 isArrayLike: true,
93 forEach: true,
94 forEachSorted: true,
95 reverseParams: true,
96 nextUid: true,
97 setHashKey: true,
98 extend: true,
99 toInt: true,
100 inherit: true,
101 merge: true,
102 noop: true,
103 identity: true,
104 valueFn: true,
105 isUndefined: true,
106 isDefined: true,
107 isObject: true,
108 isBlankObject: true,
109 isString: true,
110 isNumber: true,
111 isDate: true,
112 isArray: true,
113 isFunction: true,
114 isRegExp: true,
115 isWindow: true,
116 isScope: true,
117 isFile: true,
118 isFormData: true,
119 isBlob: true,
120 isBoolean: true,
121 isPromiseLike: true,
122 trim: true,
123 escapeForRegexp: true,
124 isElement: true,
125 makeMap: true,
126 includes: true,
127 arrayRemove: true,
128 copy: true,
129 shallowCopy: true,
130 equals: true,
131 csp: true,
132 jq: true,
133 concat: true,
134 sliceArgs: true,
135 bind: true,
136 toJsonReplacer: true,
137 toJson: true,
138 fromJson: true,
139 convertTimezoneToLocal: true,
140 timezoneToOffset: true,
141 startingTag: true,
142 tryDecodeURIComponent: true,
143 parseKeyValue: true,
144 toKeyValue: true,
145 encodeUriSegment: true,
146 encodeUriQuery: true,
147 angularInit: true,
148 bootstrap: true,
149 getTestability: true,
150 snake_case: true,
151 bindJQuery: true,
152 assertArg: true,
153 assertArgFn: true,
154 assertNotHasOwnProperty: true,
155 getter: true,
156 getBlockNodes: true,
157 hasOwnProperty: true,
158 createMap: true,
159
160 NODE_TYPE_ELEMENT: true,
161 NODE_TYPE_ATTRIBUTE: true,
162 NODE_TYPE_TEXT: true,
163 NODE_TYPE_COMMENT: true,
164 NODE_TYPE_DOCUMENT: true,
165 NODE_TYPE_DOCUMENT_FRAGMENT: true,
166*/
167
168////////////////////////////////////
169
170/**
171 * @ngdoc module
172 * @name ng
173 * @module ng
174 * @description
175 *
176 * # ng (core module)
177 * The ng module is loaded by default when an AngularJS application is started. The module itself
178 * contains the essential components for an AngularJS application to function. The table below
179 * lists a high level breakdown of each of the services/factories, filters, directives and testing
180 * components available within this core module.
181 *
182 * <div doc-module-components="ng"></div>
183 */
184
185var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
186
187// The name of a form control's ValidityState property.
188// This is used so that it's possible for internal tests to create mock ValidityStates.
189var VALIDITY_STATE_PROPERTY = 'validity';
190
191/**
192 * @ngdoc function
193 * @name angular.lowercase
194 * @module ng
195 * @kind function
196 *
197 * @description Converts the specified string to lowercase.
198 * @param {string} string String to be converted to lowercase.
199 * @returns {string} Lowercased string.
200 */
201var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
202var hasOwnProperty = Object.prototype.hasOwnProperty;
203
204/**
205 * @ngdoc function
206 * @name angular.uppercase
207 * @module ng
208 * @kind function
209 *
210 * @description Converts the specified string to uppercase.
211 * @param {string} string String to be converted to uppercase.
212 * @returns {string} Uppercased string.
213 */
214var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
215
216
217var manualLowercase = function(s) {
218 /* jshint bitwise: false */
219 return isString(s)
220 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
221 : s;
222};
223var manualUppercase = function(s) {
224 /* jshint bitwise: false */
225 return isString(s)
226 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
227 : s;
228};
229
230
231// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
232// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
233// with correct but slower alternatives.
234if ('i' !== 'I'.toLowerCase()) {
235 lowercase = manualLowercase;
236 uppercase = manualUppercase;
237}
238
239
240var
241 msie, // holds major version number for IE, or NaN if UA is not IE.
242 jqLite, // delay binding since jQuery could be loaded after us.
243 jQuery, // delay binding
244 slice = [].slice,
245 splice = [].splice,
246 push = [].push,
247 toString = Object.prototype.toString,
248 getPrototypeOf = Object.getPrototypeOf,
249 ngMinErr = minErr('ng'),
250
251 /** @name angular */
252 angular = window.angular || (window.angular = {}),
253 angularModule,
254 uid = 0;
255
256/**
257 * documentMode is an IE-only property
258 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
259 */
260msie = document.documentMode;
261
262
263/**
264 * @private
265 * @param {*} obj
266 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
267 * String ...)
268 */
269function isArrayLike(obj) {
270
271 // `null`, `undefined` and `window` are not array-like
272 if (obj == null || isWindow(obj)) return false;
273
274 // arrays, strings and jQuery/jqLite objects are array like
275 // * jqLite is either the jQuery or jqLite constructor function
276 // * we have to check the existance of jqLite first as this method is called
277 // via the forEach method when constructing the jqLite object in the first place
278 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
279
280 // Support: iOS 8.2 (not reproducible in simulator)
281 // "length" in obj used to prevent JIT error (gh-11508)
282 var length = "length" in Object(obj) && obj.length;
283
284 // NodeList objects (with `item` method) and
285 // other objects with suitable length characteristics are array-like
286 return isNumber(length) &&
287 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
288}
289
290/**
291 * @ngdoc function
292 * @name angular.forEach
293 * @module ng
294 * @kind function
295 *
296 * @description
297 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
298 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
299 * is the value of an object property or an array element, `key` is the object property key or
300 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
301 *
302 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
303 * using the `hasOwnProperty` method.
304 *
305 * Unlike ES262's
306 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
307 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
308 * return the value provided.
309 *
310 ```js
311 var values = {name: 'misko', gender: 'male'};
312 var log = [];
313 angular.forEach(values, function(value, key) {
314 this.push(key + ': ' + value);
315 }, log);
316 expect(log).toEqual(['name: misko', 'gender: male']);
317 ```
318 *
319 * @param {Object|Array} obj Object to iterate over.
320 * @param {Function} iterator Iterator function.
321 * @param {Object=} context Object to become context (`this`) for the iterator function.
322 * @returns {Object|Array} Reference to `obj`.
323 */
324
325function forEach(obj, iterator, context) {
326 var key, length;
327 if (obj) {
328 if (isFunction(obj)) {
329 for (key in obj) {
330 // Need to check if hasOwnProperty exists,
331 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
332 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
333 iterator.call(context, obj[key], key, obj);
334 }
335 }
336 } else if (isArray(obj) || isArrayLike(obj)) {
337 var isPrimitive = typeof obj !== 'object';
338 for (key = 0, length = obj.length; key < length; key++) {
339 if (isPrimitive || key in obj) {
340 iterator.call(context, obj[key], key, obj);
341 }
342 }
343 } else if (obj.forEach && obj.forEach !== forEach) {
344 obj.forEach(iterator, context, obj);
345 } else if (isBlankObject(obj)) {
346 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
347 for (key in obj) {
348 iterator.call(context, obj[key], key, obj);
349 }
350 } else if (typeof obj.hasOwnProperty === 'function') {
351 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
352 for (key in obj) {
353 if (obj.hasOwnProperty(key)) {
354 iterator.call(context, obj[key], key, obj);
355 }
356 }
357 } else {
358 // Slow path for objects which do not have a method `hasOwnProperty`
359 for (key in obj) {
360 if (hasOwnProperty.call(obj, key)) {
361 iterator.call(context, obj[key], key, obj);
362 }
363 }
364 }
365 }
366 return obj;
367}
368
369function forEachSorted(obj, iterator, context) {
370 var keys = Object.keys(obj).sort();
371 for (var i = 0; i < keys.length; i++) {
372 iterator.call(context, obj[keys[i]], keys[i]);
373 }
374 return keys;
375}
376
377
378/**
379 * when using forEach the params are value, key, but it is often useful to have key, value.
380 * @param {function(string, *)} iteratorFn
381 * @returns {function(*, string)}
382 */
383function reverseParams(iteratorFn) {
384 return function(value, key) { iteratorFn(key, value); };
385}
386
387/**
388 * A consistent way of creating unique IDs in angular.
389 *
390 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
391 * we hit number precision issues in JavaScript.
392 *
393 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
394 *
395 * @returns {number} an unique alpha-numeric string
396 */
397function nextUid() {
398 return ++uid;
399}
400
401
402/**
403 * Set or clear the hashkey for an object.
404 * @param obj object
405 * @param h the hashkey (!truthy to delete the hashkey)
406 */
407function setHashKey(obj, h) {
408 if (h) {
409 obj.$$hashKey = h;
410 } else {
411 delete obj.$$hashKey;
412 }
413}
414
415
416function baseExtend(dst, objs, deep) {
417 var h = dst.$$hashKey;
418
419 for (var i = 0, ii = objs.length; i < ii; ++i) {
420 var obj = objs[i];
421 if (!isObject(obj) && !isFunction(obj)) continue;
422 var keys = Object.keys(obj);
423 for (var j = 0, jj = keys.length; j < jj; j++) {
424 var key = keys[j];
425 var src = obj[key];
426
427 if (deep && isObject(src)) {
428 if (isDate(src)) {
429 dst[key] = new Date(src.valueOf());
430 } else if (isRegExp(src)) {
431 dst[key] = new RegExp(src);
432 } else if (src.nodeName) {
433 dst[key] = src.cloneNode(true);
434 } else if (isElement(src)) {
435 dst[key] = src.clone();
436 } else {
437 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
438 baseExtend(dst[key], [src], true);
439 }
440 } else {
441 dst[key] = src;
442 }
443 }
444 }
445
446 setHashKey(dst, h);
447 return dst;
448}
449
450/**
451 * @ngdoc function
452 * @name angular.extend
453 * @module ng
454 * @kind function
455 *
456 * @description
457 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
458 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
459 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
460 *
461 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
462 * {@link angular.merge} for this.
463 *
464 * @param {Object} dst Destination object.
465 * @param {...Object} src Source object(s).
466 * @returns {Object} Reference to `dst`.
467 */
468function extend(dst) {
469 return baseExtend(dst, slice.call(arguments, 1), false);
470}
471
472
473/**
474* @ngdoc function
475* @name angular.merge
476* @module ng
477* @kind function
478*
479* @description
480* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
481* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
482* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
483*
484* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
485* objects, performing a deep copy.
486*
487* @param {Object} dst Destination object.
488* @param {...Object} src Source object(s).
489* @returns {Object} Reference to `dst`.
490*/
491function merge(dst) {
492 return baseExtend(dst, slice.call(arguments, 1), true);
493}
494
495
496
497function toInt(str) {
498 return parseInt(str, 10);
499}
500
501
502function inherit(parent, extra) {
503 return extend(Object.create(parent), extra);
504}
505
506/**
507 * @ngdoc function
508 * @name angular.noop
509 * @module ng
510 * @kind function
511 *
512 * @description
513 * A function that performs no operations. This function can be useful when writing code in the
514 * functional style.
515 ```js
516 function foo(callback) {
517 var result = calculateResult();
518 (callback || angular.noop)(result);
519 }
520 ```
521 */
522function noop() {}
523noop.$inject = [];
524
525
526/**
527 * @ngdoc function
528 * @name angular.identity
529 * @module ng
530 * @kind function
531 *
532 * @description
533 * A function that returns its first argument. This function is useful when writing code in the
534 * functional style.
535 *
536 ```js
537 function transformer(transformationFn, value) {
538 return (transformationFn || angular.identity)(value);
539 };
540 ```
541 * @param {*} value to be returned.
542 * @returns {*} the value passed in.
543 */
544function identity($) {return $;}
545identity.$inject = [];
546
547
548function valueFn(value) {return function() {return value;};}
549
550function hasCustomToString(obj) {
551 return isFunction(obj.toString) && obj.toString !== toString;
552}
553
554
555/**
556 * @ngdoc function
557 * @name angular.isUndefined
558 * @module ng
559 * @kind function
560 *
561 * @description
562 * Determines if a reference is undefined.
563 *
564 * @param {*} value Reference to check.
565 * @returns {boolean} True if `value` is undefined.
566 */
567function isUndefined(value) {return typeof value === 'undefined';}
568
569
570/**
571 * @ngdoc function
572 * @name angular.isDefined
573 * @module ng
574 * @kind function
575 *
576 * @description
577 * Determines if a reference is defined.
578 *
579 * @param {*} value Reference to check.
580 * @returns {boolean} True if `value` is defined.
581 */
582function isDefined(value) {return typeof value !== 'undefined';}
583
584
585/**
586 * @ngdoc function
587 * @name angular.isObject
588 * @module ng
589 * @kind function
590 *
591 * @description
592 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
593 * considered to be objects. Note that JavaScript arrays are objects.
594 *
595 * @param {*} value Reference to check.
596 * @returns {boolean} True if `value` is an `Object` but not `null`.
597 */
598function isObject(value) {
599 // http://jsperf.com/isobject4
600 return value !== null && typeof value === 'object';
601}
602
603
604/**
605 * Determine if a value is an object with a null prototype
606 *
607 * @returns {boolean} True if `value` is an `Object` with a null prototype
608 */
609function isBlankObject(value) {
610 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
611}
612
613
614/**
615 * @ngdoc function
616 * @name angular.isString
617 * @module ng
618 * @kind function
619 *
620 * @description
621 * Determines if a reference is a `String`.
622 *
623 * @param {*} value Reference to check.
624 * @returns {boolean} True if `value` is a `String`.
625 */
626function isString(value) {return typeof value === 'string';}
627
628
629/**
630 * @ngdoc function
631 * @name angular.isNumber
632 * @module ng
633 * @kind function
634 *
635 * @description
636 * Determines if a reference is a `Number`.
637 *
638 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
639 *
640 * If you wish to exclude these then you can use the native
641 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
642 * method.
643 *
644 * @param {*} value Reference to check.
645 * @returns {boolean} True if `value` is a `Number`.
646 */
647function isNumber(value) {return typeof value === 'number';}
648
649
650/**
651 * @ngdoc function
652 * @name angular.isDate
653 * @module ng
654 * @kind function
655 *
656 * @description
657 * Determines if a value is a date.
658 *
659 * @param {*} value Reference to check.
660 * @returns {boolean} True if `value` is a `Date`.
661 */
662function isDate(value) {
663 return toString.call(value) === '[object Date]';
664}
665
666
667/**
668 * @ngdoc function
669 * @name angular.isArray
670 * @module ng
671 * @kind function
672 *
673 * @description
674 * Determines if a reference is an `Array`.
675 *
676 * @param {*} value Reference to check.
677 * @returns {boolean} True if `value` is an `Array`.
678 */
679var isArray = Array.isArray;
680
681/**
682 * @ngdoc function
683 * @name angular.isFunction
684 * @module ng
685 * @kind function
686 *
687 * @description
688 * Determines if a reference is a `Function`.
689 *
690 * @param {*} value Reference to check.
691 * @returns {boolean} True if `value` is a `Function`.
692 */
693function isFunction(value) {return typeof value === 'function';}
694
695
696/**
697 * Determines if a value is a regular expression object.
698 *
699 * @private
700 * @param {*} value Reference to check.
701 * @returns {boolean} True if `value` is a `RegExp`.
702 */
703function isRegExp(value) {
704 return toString.call(value) === '[object RegExp]';
705}
706
707
708/**
709 * Checks if `obj` is a window object.
710 *
711 * @private
712 * @param {*} obj Object to check
713 * @returns {boolean} True if `obj` is a window obj.
714 */
715function isWindow(obj) {
716 return obj && obj.window === obj;
717}
718
719
720function isScope(obj) {
721 return obj && obj.$evalAsync && obj.$watch;
722}
723
724
725function isFile(obj) {
726 return toString.call(obj) === '[object File]';
727}
728
729
730function isFormData(obj) {
731 return toString.call(obj) === '[object FormData]';
732}
733
734
735function isBlob(obj) {
736 return toString.call(obj) === '[object Blob]';
737}
738
739
740function isBoolean(value) {
741 return typeof value === 'boolean';
742}
743
744
745function isPromiseLike(obj) {
746 return obj && isFunction(obj.then);
747}
748
749
750var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
751function isTypedArray(value) {
752 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
753}
754
755
756var trim = function(value) {
757 return isString(value) ? value.trim() : value;
758};
759
760// Copied from:
761// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
762// Prereq: s is a string.
763var escapeForRegexp = function(s) {
764 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
765 replace(/\x08/g, '\\x08');
766};
767
768
769/**
770 * @ngdoc function
771 * @name angular.isElement
772 * @module ng
773 * @kind function
774 *
775 * @description
776 * Determines if a reference is a DOM element (or wrapped jQuery element).
777 *
778 * @param {*} value Reference to check.
779 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
780 */
781function isElement(node) {
782 return !!(node &&
783 (node.nodeName // we are a direct element
784 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
785}
786
787/**
788 * @param str 'key1,key2,...'
789 * @returns {object} in the form of {key1:true, key2:true, ...}
790 */
791function makeMap(str) {
792 var obj = {}, items = str.split(","), i;
793 for (i = 0; i < items.length; i++) {
794 obj[items[i]] = true;
795 }
796 return obj;
797}
798
799
800function nodeName_(element) {
801 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
802}
803
804function includes(array, obj) {
805 return Array.prototype.indexOf.call(array, obj) != -1;
806}
807
808function arrayRemove(array, value) {
809 var index = array.indexOf(value);
810 if (index >= 0) {
811 array.splice(index, 1);
812 }
813 return index;
814}
815
816/**
817 * @ngdoc function
818 * @name angular.copy
819 * @module ng
820 * @kind function
821 *
822 * @description
823 * Creates a deep copy of `source`, which should be an object or an array.
824 *
825 * * If no destination is supplied, a copy of the object or array is created.
826 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
827 * are deleted and then all elements/properties from the source are copied to it.
828 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
829 * * If `source` is identical to 'destination' an exception will be thrown.
830 *
831 * @param {*} source The source that will be used to make a copy.
832 * Can be any type, including primitives, `null`, and `undefined`.
833 * @param {(Object|Array)=} destination Destination into which the source is copied. If
834 * provided, must be of the same type as `source`.
835 * @returns {*} The copy or updated `destination`, if `destination` was specified.
836 *
837 * @example
838 <example module="copyExample">
839 <file name="index.html">
840 <div ng-controller="ExampleController">
841 <form novalidate class="simple-form">
842 Name: <input type="text" ng-model="user.name" /><br />
843 E-mail: <input type="email" ng-model="user.email" /><br />
844 Gender: <input type="radio" ng-model="user.gender" value="male" />male
845 <input type="radio" ng-model="user.gender" value="female" />female<br />
846 <button ng-click="reset()">RESET</button>
847 <button ng-click="update(user)">SAVE</button>
848 </form>
849 <pre>form = {{user | json}}</pre>
850 <pre>master = {{master | json}}</pre>
851 </div>
852
853 <script>
854 angular.module('copyExample', [])
855 .controller('ExampleController', ['$scope', function($scope) {
856 $scope.master= {};
857
858 $scope.update = function(user) {
859 // Example with 1 argument
860 $scope.master= angular.copy(user);
861 };
862
863 $scope.reset = function() {
864 // Example with 2 arguments
865 angular.copy($scope.master, $scope.user);
866 };
867
868 $scope.reset();
869 }]);
870 </script>
871 </file>
872 </example>
873 */
874function copy(source, destination) {
875 var stackSource = [];
876 var stackDest = [];
877
878 if (destination) {
879 if (isTypedArray(destination)) {
880 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
881 }
882 if (source === destination) {
883 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
884 }
885
886 // Empty the destination object
887 if (isArray(destination)) {
888 destination.length = 0;
889 } else {
890 forEach(destination, function(value, key) {
891 if (key !== '$$hashKey') {
892 delete destination[key];
893 }
894 });
895 }
896
897 stackSource.push(source);
898 stackDest.push(destination);
899 return copyRecurse(source, destination);
900 }
901
902 return copyElement(source);
903
904 function copyRecurse(source, destination) {
905 var h = destination.$$hashKey;
906 var result, key;
907 if (isArray(source)) {
908 for (var i = 0, ii = source.length; i < ii; i++) {
909 destination.push(copyElement(source[i]));
910 }
911 } else if (isBlankObject(source)) {
912 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
913 for (key in source) {
914 destination[key] = copyElement(source[key]);
915 }
916 } else if (source && typeof source.hasOwnProperty === 'function') {
917 // Slow path, which must rely on hasOwnProperty
918 for (key in source) {
919 if (source.hasOwnProperty(key)) {
920 destination[key] = copyElement(source[key]);
921 }
922 }
923 } else {
924 // Slowest path --- hasOwnProperty can't be called as a method
925 for (key in source) {
926 if (hasOwnProperty.call(source, key)) {
927 destination[key] = copyElement(source[key]);
928 }
929 }
930 }
931 setHashKey(destination, h);
932 return destination;
933 }
934
935 function copyElement(source) {
936 // Simple values
937 if (!isObject(source)) {
938 return source;
939 }
940
941 // Already copied values
942 var index = stackSource.indexOf(source);
943 if (index !== -1) {
944 return stackDest[index];
945 }
946
947 if (isWindow(source) || isScope(source)) {
948 throw ngMinErr('cpws',
949 "Can't copy! Making copies of Window or Scope instances is not supported.");
950 }
951
952 var needsRecurse = false;
953 var destination;
954
955 if (isArray(source)) {
956 destination = [];
957 needsRecurse = true;
958 } else if (isTypedArray(source)) {
959 destination = new source.constructor(source);
960 } else if (isDate(source)) {
961 destination = new Date(source.getTime());
962 } else if (isRegExp(source)) {
963 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
964 destination.lastIndex = source.lastIndex;
965 } else if (isFunction(source.cloneNode)) {
966 destination = source.cloneNode(true);
967 } else {
968 destination = Object.create(getPrototypeOf(source));
969 needsRecurse = true;
970 }
971
972 stackSource.push(source);
973 stackDest.push(destination);
974
975 return needsRecurse
976 ? copyRecurse(source, destination)
977 : destination;
978 }
979}
980
981/**
982 * Creates a shallow copy of an object, an array or a primitive.
983 *
984 * Assumes that there are no proto properties for objects.
985 */
986function shallowCopy(src, dst) {
987 if (isArray(src)) {
988 dst = dst || [];
989
990 for (var i = 0, ii = src.length; i < ii; i++) {
991 dst[i] = src[i];
992 }
993 } else if (isObject(src)) {
994 dst = dst || {};
995
996 for (var key in src) {
997 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
998 dst[key] = src[key];
999 }
1000 }
1001 }
1002
1003 return dst || src;
1004}
1005
1006
1007/**
1008 * @ngdoc function
1009 * @name angular.equals
1010 * @module ng
1011 * @kind function
1012 *
1013 * @description
1014 * Determines if two objects or two values are equivalent. Supports value types, regular
1015 * expressions, arrays and objects.
1016 *
1017 * Two objects or values are considered equivalent if at least one of the following is true:
1018 *
1019 * * Both objects or values pass `===` comparison.
1020 * * Both objects or values are of the same type and all of their properties are equal by
1021 * comparing them with `angular.equals`.
1022 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1023 * * Both values represent the same regular expression (In JavaScript,
1024 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1025 * representation matches).
1026 *
1027 * During a property comparison, properties of `function` type and properties with names
1028 * that begin with `$` are ignored.
1029 *
1030 * Scope and DOMWindow objects are being compared only by identify (`===`).
1031 *
1032 * @param {*} o1 Object or value to compare.
1033 * @param {*} o2 Object or value to compare.
1034 * @returns {boolean} True if arguments are equal.
1035 */
1036function equals(o1, o2) {
1037 if (o1 === o2) return true;
1038 if (o1 === null || o2 === null) return false;
1039 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1040 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1041 if (t1 == t2) {
1042 if (t1 == 'object') {
1043 if (isArray(o1)) {
1044 if (!isArray(o2)) return false;
1045 if ((length = o1.length) == o2.length) {
1046 for (key = 0; key < length; key++) {
1047 if (!equals(o1[key], o2[key])) return false;
1048 }
1049 return true;
1050 }
1051 } else if (isDate(o1)) {
1052 if (!isDate(o2)) return false;
1053 return equals(o1.getTime(), o2.getTime());
1054 } else if (isRegExp(o1)) {
1055 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1056 } else {
1057 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1058 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1059 keySet = createMap();
1060 for (key in o1) {
1061 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1062 if (!equals(o1[key], o2[key])) return false;
1063 keySet[key] = true;
1064 }
1065 for (key in o2) {
1066 if (!(key in keySet) &&
1067 key.charAt(0) !== '$' &&
1068 isDefined(o2[key]) &&
1069 !isFunction(o2[key])) return false;
1070 }
1071 return true;
1072 }
1073 }
1074 }
1075 return false;
1076}
1077
1078var csp = function() {
1079 if (!isDefined(csp.rules)) {
1080
1081
1082 var ngCspElement = (document.querySelector('[ng-csp]') ||
1083 document.querySelector('[data-ng-csp]'));
1084
1085 if (ngCspElement) {
1086 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1087 ngCspElement.getAttribute('data-ng-csp');
1088 csp.rules = {
1089 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1090 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1091 };
1092 } else {
1093 csp.rules = {
1094 noUnsafeEval: noUnsafeEval(),
1095 noInlineStyle: false
1096 };
1097 }
1098 }
1099
1100 return csp.rules;
1101
1102 function noUnsafeEval() {
1103 try {
1104 /* jshint -W031, -W054 */
1105 new Function('');
1106 /* jshint +W031, +W054 */
1107 return false;
1108 } catch (e) {
1109 return true;
1110 }
1111 }
1112};
1113
1114/**
1115 * @ngdoc directive
1116 * @module ng
1117 * @name ngJq
1118 *
1119 * @element ANY
1120 * @param {string=} ngJq the name of the library available under `window`
1121 * to be used for angular.element
1122 * @description
1123 * Use this directive to force the angular.element library. This should be
1124 * used to force either jqLite by leaving ng-jq blank or setting the name of
1125 * the jquery variable under window (eg. jQuery).
1126 *
1127 * Since angular looks for this directive when it is loaded (doesn't wait for the
1128 * DOMContentLoaded event), it must be placed on an element that comes before the script
1129 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1130 * others ignored.
1131 *
1132 * @example
1133 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1134 ```html
1135 <!doctype html>
1136 <html ng-app ng-jq>
1137 ...
1138 ...
1139 </html>
1140 ```
1141 * @example
1142 * This example shows how to use a jQuery based library of a different name.
1143 * The library name must be available at the top most 'window'.
1144 ```html
1145 <!doctype html>
1146 <html ng-app ng-jq="jQueryLib">
1147 ...
1148 ...
1149 </html>
1150 ```
1151 */
1152var jq = function() {
1153 if (isDefined(jq.name_)) return jq.name_;
1154 var el;
1155 var i, ii = ngAttrPrefixes.length, prefix, name;
1156 for (i = 0; i < ii; ++i) {
1157 prefix = ngAttrPrefixes[i];
1158 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1159 name = el.getAttribute(prefix + 'jq');
1160 break;
1161 }
1162 }
1163
1164 return (jq.name_ = name);
1165};
1166
1167function concat(array1, array2, index) {
1168 return array1.concat(slice.call(array2, index));
1169}
1170
1171function sliceArgs(args, startIndex) {
1172 return slice.call(args, startIndex || 0);
1173}
1174
1175
1176/* jshint -W101 */
1177/**
1178 * @ngdoc function
1179 * @name angular.bind
1180 * @module ng
1181 * @kind function
1182 *
1183 * @description
1184 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1185 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1186 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1187 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1188 *
1189 * @param {Object} self Context which `fn` should be evaluated in.
1190 * @param {function()} fn Function to be bound.
1191 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1192 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1193 */
1194/* jshint +W101 */
1195function bind(self, fn) {
1196 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1197 if (isFunction(fn) && !(fn instanceof RegExp)) {
1198 return curryArgs.length
1199 ? function() {
1200 return arguments.length
1201 ? fn.apply(self, concat(curryArgs, arguments, 0))
1202 : fn.apply(self, curryArgs);
1203 }
1204 : function() {
1205 return arguments.length
1206 ? fn.apply(self, arguments)
1207 : fn.call(self);
1208 };
1209 } else {
1210 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1211 return fn;
1212 }
1213}
1214
1215
1216function toJsonReplacer(key, value) {
1217 var val = value;
1218
1219 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1220 val = undefined;
1221 } else if (isWindow(value)) {
1222 val = '$WINDOW';
1223 } else if (value && document === value) {
1224 val = '$DOCUMENT';
1225 } else if (isScope(value)) {
1226 val = '$SCOPE';
1227 }
1228
1229 return val;
1230}
1231
1232
1233/**
1234 * @ngdoc function
1235 * @name angular.toJson
1236 * @module ng
1237 * @kind function
1238 *
1239 * @description
1240 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1241 * stripped since angular uses this notation internally.
1242 *
1243 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1244 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1245 * If set to an integer, the JSON output will contain that many spaces per indentation.
1246 * @returns {string|undefined} JSON-ified string representing `obj`.
1247 */
1248function toJson(obj, pretty) {
1249 if (typeof obj === 'undefined') return undefined;
1250 if (!isNumber(pretty)) {
1251 pretty = pretty ? 2 : null;
1252 }
1253 return JSON.stringify(obj, toJsonReplacer, pretty);
1254}
1255
1256
1257/**
1258 * @ngdoc function
1259 * @name angular.fromJson
1260 * @module ng
1261 * @kind function
1262 *
1263 * @description
1264 * Deserializes a JSON string.
1265 *
1266 * @param {string} json JSON string to deserialize.
1267 * @returns {Object|Array|string|number} Deserialized JSON string.
1268 */
1269function fromJson(json) {
1270 return isString(json)
1271 ? JSON.parse(json)
1272 : json;
1273}
1274
1275
1276function timezoneToOffset(timezone, fallback) {
1277 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1278 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1279}
1280
1281
1282function addDateMinutes(date, minutes) {
1283 date = new Date(date.getTime());
1284 date.setMinutes(date.getMinutes() + minutes);
1285 return date;
1286}
1287
1288
1289function convertTimezoneToLocal(date, timezone, reverse) {
1290 reverse = reverse ? -1 : 1;
1291 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1292 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1293}
1294
1295
1296/**
1297 * @returns {string} Returns the string representation of the element.
1298 */
1299function startingTag(element) {
1300 element = jqLite(element).clone();
1301 try {
1302 // turns out IE does not let you set .html() on elements which
1303 // are not allowed to have children. So we just ignore it.
1304 element.empty();
1305 } catch (e) {}
1306 var elemHtml = jqLite('<div>').append(element).html();
1307 try {
1308 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1309 elemHtml.
1310 match(/^(<[^>]+>)/)[1].
1311 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1312 } catch (e) {
1313 return lowercase(elemHtml);
1314 }
1315
1316}
1317
1318
1319/////////////////////////////////////////////////
1320
1321/**
1322 * Tries to decode the URI component without throwing an exception.
1323 *
1324 * @private
1325 * @param str value potential URI component to check.
1326 * @returns {boolean} True if `value` can be decoded
1327 * with the decodeURIComponent function.
1328 */
1329function tryDecodeURIComponent(value) {
1330 try {
1331 return decodeURIComponent(value);
1332 } catch (e) {
1333 // Ignore any invalid uri component
1334 }
1335}
1336
1337
1338/**
1339 * Parses an escaped url query string into key-value pairs.
1340 * @returns {Object.<string,boolean|Array>}
1341 */
1342function parseKeyValue(/**string*/keyValue) {
1343 var obj = {};
1344 forEach((keyValue || "").split('&'), function(keyValue) {
1345 var splitPoint, key, val;
1346 if (keyValue) {
1347 key = keyValue = keyValue.replace(/\+/g,'%20');
1348 splitPoint = keyValue.indexOf('=');
1349 if (splitPoint !== -1) {
1350 key = keyValue.substring(0, splitPoint);
1351 val = keyValue.substring(splitPoint + 1);
1352 }
1353 key = tryDecodeURIComponent(key);
1354 if (isDefined(key)) {
1355 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1356 if (!hasOwnProperty.call(obj, key)) {
1357 obj[key] = val;
1358 } else if (isArray(obj[key])) {
1359 obj[key].push(val);
1360 } else {
1361 obj[key] = [obj[key],val];
1362 }
1363 }
1364 }
1365 });
1366 return obj;
1367}
1368
1369function toKeyValue(obj) {
1370 var parts = [];
1371 forEach(obj, function(value, key) {
1372 if (isArray(value)) {
1373 forEach(value, function(arrayValue) {
1374 parts.push(encodeUriQuery(key, true) +
1375 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1376 });
1377 } else {
1378 parts.push(encodeUriQuery(key, true) +
1379 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1380 }
1381 });
1382 return parts.length ? parts.join('&') : '';
1383}
1384
1385
1386/**
1387 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1388 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1389 * segments:
1390 * segment = *pchar
1391 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1392 * pct-encoded = "%" HEXDIG HEXDIG
1393 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1394 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1395 * / "*" / "+" / "," / ";" / "="
1396 */
1397function encodeUriSegment(val) {
1398 return encodeUriQuery(val, true).
1399 replace(/%26/gi, '&').
1400 replace(/%3D/gi, '=').
1401 replace(/%2B/gi, '+');
1402}
1403
1404
1405/**
1406 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1407 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1408 * encoded per http://tools.ietf.org/html/rfc3986:
1409 * query = *( pchar / "/" / "?" )
1410 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1411 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1412 * pct-encoded = "%" HEXDIG HEXDIG
1413 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1414 * / "*" / "+" / "," / ";" / "="
1415 */
1416function encodeUriQuery(val, pctEncodeSpaces) {
1417 return encodeURIComponent(val).
1418 replace(/%40/gi, '@').
1419 replace(/%3A/gi, ':').
1420 replace(/%24/g, '$').
1421 replace(/%2C/gi, ',').
1422 replace(/%3B/gi, ';').
1423 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1424}
1425
1426var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1427
1428function getNgAttribute(element, ngAttr) {
1429 var attr, i, ii = ngAttrPrefixes.length;
1430 for (i = 0; i < ii; ++i) {
1431 attr = ngAttrPrefixes[i] + ngAttr;
1432 if (isString(attr = element.getAttribute(attr))) {
1433 return attr;
1434 }
1435 }
1436 return null;
1437}
1438
1439/**
1440 * @ngdoc directive
1441 * @name ngApp
1442 * @module ng
1443 *
1444 * @element ANY
1445 * @param {angular.Module} ngApp an optional application
1446 * {@link angular.module module} name to load.
1447 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1448 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1449 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1450 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1451 * tracking down the root of these bugs.
1452 *
1453 * @description
1454 *
1455 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1456 * designates the **root element** of the application and is typically placed near the root element
1457 * of the page - e.g. on the `<body>` or `<html>` tags.
1458 *
1459 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1460 * found in the document will be used to define the root element to auto-bootstrap as an
1461 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1462 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1463 *
1464 * You can specify an **AngularJS module** to be used as the root module for the application. This
1465 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1466 * should contain the application code needed or have dependencies on other modules that will
1467 * contain the code. See {@link angular.module} for more information.
1468 *
1469 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1470 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1471 * would not be resolved to `3`.
1472 *
1473 * `ngApp` is the easiest, and most common way to bootstrap an application.
1474 *
1475 <example module="ngAppDemo">
1476 <file name="index.html">
1477 <div ng-controller="ngAppDemoController">
1478 I can add: {{a}} + {{b}} = {{ a+b }}
1479 </div>
1480 </file>
1481 <file name="script.js">
1482 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1483 $scope.a = 1;
1484 $scope.b = 2;
1485 });
1486 </file>
1487 </example>
1488 *
1489 * Using `ngStrictDi`, you would see something like this:
1490 *
1491 <example ng-app-included="true">
1492 <file name="index.html">
1493 <div ng-app="ngAppStrictDemo" ng-strict-di>
1494 <div ng-controller="GoodController1">
1495 I can add: {{a}} + {{b}} = {{ a+b }}
1496
1497 <p>This renders because the controller does not fail to
1498 instantiate, by using explicit annotation style (see
1499 script.js for details)
1500 </p>
1501 </div>
1502
1503 <div ng-controller="GoodController2">
1504 Name: <input ng-model="name"><br />
1505 Hello, {{name}}!
1506
1507 <p>This renders because the controller does not fail to
1508 instantiate, by using explicit annotation style
1509 (see script.js for details)
1510 </p>
1511 </div>
1512
1513 <div ng-controller="BadController">
1514 I can add: {{a}} + {{b}} = {{ a+b }}
1515
1516 <p>The controller could not be instantiated, due to relying
1517 on automatic function annotations (which are disabled in
1518 strict mode). As such, the content of this section is not
1519 interpolated, and there should be an error in your web console.
1520 </p>
1521 </div>
1522 </div>
1523 </file>
1524 <file name="script.js">
1525 angular.module('ngAppStrictDemo', [])
1526 // BadController will fail to instantiate, due to relying on automatic function annotation,
1527 // rather than an explicit annotation
1528 .controller('BadController', function($scope) {
1529 $scope.a = 1;
1530 $scope.b = 2;
1531 })
1532 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1533 // due to using explicit annotations using the array style and $inject property, respectively.
1534 .controller('GoodController1', ['$scope', function($scope) {
1535 $scope.a = 1;
1536 $scope.b = 2;
1537 }])
1538 .controller('GoodController2', GoodController2);
1539 function GoodController2($scope) {
1540 $scope.name = "World";
1541 }
1542 GoodController2.$inject = ['$scope'];
1543 </file>
1544 <file name="style.css">
1545 div[ng-controller] {
1546 margin-bottom: 1em;
1547 -webkit-border-radius: 4px;
1548 border-radius: 4px;
1549 border: 1px solid;
1550 padding: .5em;
1551 }
1552 div[ng-controller^=Good] {
1553 border-color: #d6e9c6;
1554 background-color: #dff0d8;
1555 color: #3c763d;
1556 }
1557 div[ng-controller^=Bad] {
1558 border-color: #ebccd1;
1559 background-color: #f2dede;
1560 color: #a94442;
1561 margin-bottom: 0;
1562 }
1563 </file>
1564 </example>
1565 */
1566function angularInit(element, bootstrap) {
1567 var appElement,
1568 module,
1569 config = {};
1570
1571 // The element `element` has priority over any other element
1572 forEach(ngAttrPrefixes, function(prefix) {
1573 var name = prefix + 'app';
1574
1575 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1576 appElement = element;
1577 module = element.getAttribute(name);
1578 }
1579 });
1580 forEach(ngAttrPrefixes, function(prefix) {
1581 var name = prefix + 'app';
1582 var candidate;
1583
1584 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1585 appElement = candidate;
1586 module = candidate.getAttribute(name);
1587 }
1588 });
1589 if (appElement) {
1590 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1591 bootstrap(appElement, module ? [module] : [], config);
1592 }
1593}
1594
1595/**
1596 * @ngdoc function
1597 * @name angular.bootstrap
1598 * @module ng
1599 * @description
1600 * Use this function to manually start up angular application.
1601 *
1602 * See: {@link guide/bootstrap Bootstrap}
1603 *
1604 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1605 * They must use {@link ng.directive:ngApp ngApp}.
1606 *
1607 * Angular will detect if it has been loaded into the browser more than once and only allow the
1608 * first loaded script to be bootstrapped and will report a warning to the browser console for
1609 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1610 * multiple instances of Angular try to work on the DOM.
1611 *
1612 * ```html
1613 * <!doctype html>
1614 * <html>
1615 * <body>
1616 * <div ng-controller="WelcomeController">
1617 * {{greeting}}
1618 * </div>
1619 *
1620 * <script src="angular.js"></script>
1621 * <script>
1622 * var app = angular.module('demo', [])
1623 * .controller('WelcomeController', function($scope) {
1624 * $scope.greeting = 'Welcome!';
1625 * });
1626 * angular.bootstrap(document, ['demo']);
1627 * </script>
1628 * </body>
1629 * </html>
1630 * ```
1631 *
1632 * @param {DOMElement} element DOM element which is the root of angular application.
1633 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1634 * Each item in the array should be the name of a predefined module or a (DI annotated)
1635 * function that will be invoked by the injector as a `config` block.
1636 * See: {@link angular.module modules}
1637 * @param {Object=} config an object for defining configuration options for the application. The
1638 * following keys are supported:
1639 *
1640 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1641 * assist in finding bugs which break minified code. Defaults to `false`.
1642 *
1643 * @returns {auto.$injector} Returns the newly created injector for this app.
1644 */
1645function bootstrap(element, modules, config) {
1646 if (!isObject(config)) config = {};
1647 var defaultConfig = {
1648 strictDi: false
1649 };
1650 config = extend(defaultConfig, config);
1651 var doBootstrap = function() {
1652 element = jqLite(element);
1653
1654 if (element.injector()) {
1655 var tag = (element[0] === document) ? 'document' : startingTag(element);
1656 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1657 throw ngMinErr(
1658 'btstrpd',
1659 "App Already Bootstrapped with this Element '{0}'",
1660 tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
1661 }
1662
1663 modules = modules || [];
1664 modules.unshift(['$provide', function($provide) {
1665 $provide.value('$rootElement', element);
1666 }]);
1667
1668 if (config.debugInfoEnabled) {
1669 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1670 modules.push(['$compileProvider', function($compileProvider) {
1671 $compileProvider.debugInfoEnabled(true);
1672 }]);
1673 }
1674
1675 modules.unshift('ng');
1676 var injector = createInjector(modules, config.strictDi);
1677 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1678 function bootstrapApply(scope, element, compile, injector) {
1679 scope.$apply(function() {
1680 element.data('$injector', injector);
1681 compile(element)(scope);
1682 });
1683 }]
1684 );
1685 return injector;
1686 };
1687
1688 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1689 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1690
1691 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1692 config.debugInfoEnabled = true;
1693 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1694 }
1695
1696 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1697 return doBootstrap();
1698 }
1699
1700 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1701 angular.resumeBootstrap = function(extraModules) {
1702 forEach(extraModules, function(module) {
1703 modules.push(module);
1704 });
1705 return doBootstrap();
1706 };
1707
1708 if (isFunction(angular.resumeDeferredBootstrap)) {
1709 angular.resumeDeferredBootstrap();
1710 }
1711}
1712
1713/**
1714 * @ngdoc function
1715 * @name angular.reloadWithDebugInfo
1716 * @module ng
1717 * @description
1718 * Use this function to reload the current application with debug information turned on.
1719 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1720 *
1721 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1722 */
1723function reloadWithDebugInfo() {
1724 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1725 window.location.reload();
1726}
1727
1728/**
1729 * @name angular.getTestability
1730 * @module ng
1731 * @description
1732 * Get the testability service for the instance of Angular on the given
1733 * element.
1734 * @param {DOMElement} element DOM element which is the root of angular application.
1735 */
1736function getTestability(rootElement) {
1737 var injector = angular.element(rootElement).injector();
1738 if (!injector) {
1739 throw ngMinErr('test',
1740 'no injector found for element argument to getTestability');
1741 }
1742 return injector.get('$$testability');
1743}
1744
1745var SNAKE_CASE_REGEXP = /[A-Z]/g;
1746function snake_case(name, separator) {
1747 separator = separator || '_';
1748 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1749 return (pos ? separator : '') + letter.toLowerCase();
1750 });
1751}
1752
1753var bindJQueryFired = false;
1754var skipDestroyOnNextJQueryCleanData;
1755function bindJQuery() {
1756 var originalCleanData;
1757
1758 if (bindJQueryFired) {
1759 return;
1760 }
1761
1762 // bind to jQuery if present;
1763 var jqName = jq();
1764 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1765 !jqName ? undefined : // use jqLite
1766 window[jqName]; // use jQuery specified by `ngJq`
1767
1768 // Use jQuery if it exists with proper functionality, otherwise default to us.
1769 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1770 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1771 // versions. It will not work for sure with jQuery <1.7, though.
1772 if (jQuery && jQuery.fn.on) {
1773 jqLite = jQuery;
1774 extend(jQuery.fn, {
1775 scope: JQLitePrototype.scope,
1776 isolateScope: JQLitePrototype.isolateScope,
1777 controller: JQLitePrototype.controller,
1778 injector: JQLitePrototype.injector,
1779 inheritedData: JQLitePrototype.inheritedData
1780 });
1781
1782 // All nodes removed from the DOM via various jQuery APIs like .remove()
1783 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1784 // the $destroy event on all removed nodes.
1785 originalCleanData = jQuery.cleanData;
1786 jQuery.cleanData = function(elems) {
1787 var events;
1788 if (!skipDestroyOnNextJQueryCleanData) {
1789 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1790 events = jQuery._data(elem, "events");
1791 if (events && events.$destroy) {
1792 jQuery(elem).triggerHandler('$destroy');
1793 }
1794 }
1795 } else {
1796 skipDestroyOnNextJQueryCleanData = false;
1797 }
1798 originalCleanData(elems);
1799 };
1800 } else {
1801 jqLite = JQLite;
1802 }
1803
1804 angular.element = jqLite;
1805
1806 // Prevent double-proxying.
1807 bindJQueryFired = true;
1808}
1809
1810/**
1811 * throw error if the argument is falsy.
1812 */
1813function assertArg(arg, name, reason) {
1814 if (!arg) {
1815 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1816 }
1817 return arg;
1818}
1819
1820function assertArgFn(arg, name, acceptArrayAnnotation) {
1821 if (acceptArrayAnnotation && isArray(arg)) {
1822 arg = arg[arg.length - 1];
1823 }
1824
1825 assertArg(isFunction(arg), name, 'not a function, got ' +
1826 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1827 return arg;
1828}
1829
1830/**
1831 * throw error if the name given is hasOwnProperty
1832 * @param {String} name the name to test
1833 * @param {String} context the context in which the name is used, such as module or directive
1834 */
1835function assertNotHasOwnProperty(name, context) {
1836 if (name === 'hasOwnProperty') {
1837 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1838 }
1839}
1840
1841/**
1842 * Return the value accessible from the object by path. Any undefined traversals are ignored
1843 * @param {Object} obj starting object
1844 * @param {String} path path to traverse
1845 * @param {boolean} [bindFnToScope=true]
1846 * @returns {Object} value as accessible by path
1847 */
1848//TODO(misko): this function needs to be removed
1849function getter(obj, path, bindFnToScope) {
1850 if (!path) return obj;
1851 var keys = path.split('.');
1852 var key;
1853 var lastInstance = obj;
1854 var len = keys.length;
1855
1856 for (var i = 0; i < len; i++) {
1857 key = keys[i];
1858 if (obj) {
1859 obj = (lastInstance = obj)[key];
1860 }
1861 }
1862 if (!bindFnToScope && isFunction(obj)) {
1863 return bind(lastInstance, obj);
1864 }
1865 return obj;
1866}
1867
1868/**
1869 * Return the DOM siblings between the first and last node in the given array.
1870 * @param {Array} array like object
1871 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1872 */
1873function getBlockNodes(nodes) {
1874 // TODO(perf): update `nodes` instead of creating a new object?
1875 var node = nodes[0];
1876 var endNode = nodes[nodes.length - 1];
1877 var blockNodes;
1878
1879 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1880 if (blockNodes || nodes[i] !== node) {
1881 if (!blockNodes) {
1882 blockNodes = jqLite(slice.call(nodes, 0, i));
1883 }
1884 blockNodes.push(node);
1885 }
1886 }
1887
1888 return blockNodes || nodes;
1889}
1890
1891
1892/**
1893 * Creates a new object without a prototype. This object is useful for lookup without having to
1894 * guard against prototypically inherited properties via hasOwnProperty.
1895 *
1896 * Related micro-benchmarks:
1897 * - http://jsperf.com/object-create2
1898 * - http://jsperf.com/proto-map-lookup/2
1899 * - http://jsperf.com/for-in-vs-object-keys2
1900 *
1901 * @returns {Object}
1902 */
1903function createMap() {
1904 return Object.create(null);
1905}
1906
1907var NODE_TYPE_ELEMENT = 1;
1908var NODE_TYPE_ATTRIBUTE = 2;
1909var NODE_TYPE_TEXT = 3;
1910var NODE_TYPE_COMMENT = 8;
1911var NODE_TYPE_DOCUMENT = 9;
1912var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1913
1914/**
1915 * @ngdoc type
1916 * @name angular.Module
1917 * @module ng
1918 * @description
1919 *
1920 * Interface for configuring angular {@link angular.module modules}.
1921 */
1922
1923function setupModuleLoader(window) {
1924
1925 var $injectorMinErr = minErr('$injector');
1926 var ngMinErr = minErr('ng');
1927
1928 function ensure(obj, name, factory) {
1929 return obj[name] || (obj[name] = factory());
1930 }
1931
1932 var angular = ensure(window, 'angular', Object);
1933
1934 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
1935 angular.$$minErr = angular.$$minErr || minErr;
1936
1937 return ensure(angular, 'module', function() {
1938 /** @type {Object.<string, angular.Module>} */
1939 var modules = {};
1940
1941 /**
1942 * @ngdoc function
1943 * @name angular.module
1944 * @module ng
1945 * @description
1946 *
1947 * The `angular.module` is a global place for creating, registering and retrieving Angular
1948 * modules.
1949 * All modules (angular core or 3rd party) that should be available to an application must be
1950 * registered using this mechanism.
1951 *
1952 * Passing one argument retrieves an existing {@link angular.Module},
1953 * whereas passing more than one argument creates a new {@link angular.Module}
1954 *
1955 *
1956 * # Module
1957 *
1958 * A module is a collection of services, directives, controllers, filters, and configuration information.
1959 * `angular.module` is used to configure the {@link auto.$injector $injector}.
1960 *
1961 * ```js
1962 * // Create a new module
1963 * var myModule = angular.module('myModule', []);
1964 *
1965 * // register a new service
1966 * myModule.value('appName', 'MyCoolApp');
1967 *
1968 * // configure existing services inside initialization blocks.
1969 * myModule.config(['$locationProvider', function($locationProvider) {
1970 * // Configure existing providers
1971 * $locationProvider.hashPrefix('!');
1972 * }]);
1973 * ```
1974 *
1975 * Then you can create an injector and load your modules like this:
1976 *
1977 * ```js
1978 * var injector = angular.injector(['ng', 'myModule'])
1979 * ```
1980 *
1981 * However it's more likely that you'll just use
1982 * {@link ng.directive:ngApp ngApp} or
1983 * {@link angular.bootstrap} to simplify this process for you.
1984 *
1985 * @param {!string} name The name of the module to create or retrieve.
1986 * @param {!Array.<string>=} requires If specified then new module is being created. If
1987 * unspecified then the module is being retrieved for further configuration.
1988 * @param {Function=} configFn Optional configuration function for the module. Same as
1989 * {@link angular.Module#config Module#config()}.
1990 * @returns {module} new module with the {@link angular.Module} api.
1991 */
1992 return function module(name, requires, configFn) {
1993 var assertNotHasOwnProperty = function(name, context) {
1994 if (name === 'hasOwnProperty') {
1995 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
1996 }
1997 };
1998
1999 assertNotHasOwnProperty(name, 'module');
2000 if (requires && modules.hasOwnProperty(name)) {
2001 modules[name] = null;
2002 }
2003 return ensure(modules, name, function() {
2004 if (!requires) {
2005 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2006 "the module name or forgot to load it. If registering a module ensure that you " +
2007 "specify the dependencies as the second argument.", name);
2008 }
2009
2010 /** @type {!Array.<Array.<*>>} */
2011 var invokeQueue = [];
2012
2013 /** @type {!Array.<Function>} */
2014 var configBlocks = [];
2015
2016 /** @type {!Array.<Function>} */
2017 var runBlocks = [];
2018
2019 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2020
2021 /** @type {angular.Module} */
2022 var moduleInstance = {
2023 // Private state
2024 _invokeQueue: invokeQueue,
2025 _configBlocks: configBlocks,
2026 _runBlocks: runBlocks,
2027
2028 /**
2029 * @ngdoc property
2030 * @name angular.Module#requires
2031 * @module ng
2032 *
2033 * @description
2034 * Holds the list of modules which the injector will load before the current module is
2035 * loaded.
2036 */
2037 requires: requires,
2038
2039 /**
2040 * @ngdoc property
2041 * @name angular.Module#name
2042 * @module ng
2043 *
2044 * @description
2045 * Name of the module.
2046 */
2047 name: name,
2048
2049
2050 /**
2051 * @ngdoc method
2052 * @name angular.Module#provider
2053 * @module ng
2054 * @param {string} name service name
2055 * @param {Function} providerType Construction function for creating new instance of the
2056 * service.
2057 * @description
2058 * See {@link auto.$provide#provider $provide.provider()}.
2059 */
2060 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2061
2062 /**
2063 * @ngdoc method
2064 * @name angular.Module#factory
2065 * @module ng
2066 * @param {string} name service name
2067 * @param {Function} providerFunction Function for creating new instance of the service.
2068 * @description
2069 * See {@link auto.$provide#factory $provide.factory()}.
2070 */
2071 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2072
2073 /**
2074 * @ngdoc method
2075 * @name angular.Module#service
2076 * @module ng
2077 * @param {string} name service name
2078 * @param {Function} constructor A constructor function that will be instantiated.
2079 * @description
2080 * See {@link auto.$provide#service $provide.service()}.
2081 */
2082 service: invokeLaterAndSetModuleName('$provide', 'service'),
2083
2084 /**
2085 * @ngdoc method
2086 * @name angular.Module#value
2087 * @module ng
2088 * @param {string} name service name
2089 * @param {*} object Service instance object.
2090 * @description
2091 * See {@link auto.$provide#value $provide.value()}.
2092 */
2093 value: invokeLater('$provide', 'value'),
2094
2095 /**
2096 * @ngdoc method
2097 * @name angular.Module#constant
2098 * @module ng
2099 * @param {string} name constant name
2100 * @param {*} object Constant value.
2101 * @description
2102 * Because the constants are fixed, they get applied before other provide methods.
2103 * See {@link auto.$provide#constant $provide.constant()}.
2104 */
2105 constant: invokeLater('$provide', 'constant', 'unshift'),
2106
2107 /**
2108 * @ngdoc method
2109 * @name angular.Module#decorator
2110 * @module ng
2111 * @param {string} The name of the service to decorate.
2112 * @param {Function} This function will be invoked when the service needs to be
2113 * instantiated and should return the decorated service instance.
2114 * @description
2115 * See {@link auto.$provide#decorator $provide.decorator()}.
2116 */
2117 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2118
2119 /**
2120 * @ngdoc method
2121 * @name angular.Module#animation
2122 * @module ng
2123 * @param {string} name animation name
2124 * @param {Function} animationFactory Factory function for creating new instance of an
2125 * animation.
2126 * @description
2127 *
2128 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2129 *
2130 *
2131 * Defines an animation hook that can be later used with
2132 * {@link $animate $animate} service and directives that use this service.
2133 *
2134 * ```js
2135 * module.animation('.animation-name', function($inject1, $inject2) {
2136 * return {
2137 * eventName : function(element, done) {
2138 * //code to run the animation
2139 * //once complete, then run done()
2140 * return function cancellationFunction(element) {
2141 * //code to cancel the animation
2142 * }
2143 * }
2144 * }
2145 * })
2146 * ```
2147 *
2148 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2149 * {@link ngAnimate ngAnimate module} for more information.
2150 */
2151 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2152
2153 /**
2154 * @ngdoc method
2155 * @name angular.Module#filter
2156 * @module ng
2157 * @param {string} name Filter name - this must be a valid angular expression identifier
2158 * @param {Function} filterFactory Factory function for creating new instance of filter.
2159 * @description
2160 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2161 *
2162 * <div class="alert alert-warning">
2163 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2164 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2165 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2166 * (`myapp_subsection_filterx`).
2167 * </div>
2168 */
2169 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2170
2171 /**
2172 * @ngdoc method
2173 * @name angular.Module#controller
2174 * @module ng
2175 * @param {string|Object} name Controller name, or an object map of controllers where the
2176 * keys are the names and the values are the constructors.
2177 * @param {Function} constructor Controller constructor function.
2178 * @description
2179 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2180 */
2181 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2182
2183 /**
2184 * @ngdoc method
2185 * @name angular.Module#directive
2186 * @module ng
2187 * @param {string|Object} name Directive name, or an object map of directives where the
2188 * keys are the names and the values are the factories.
2189 * @param {Function} directiveFactory Factory function for creating new instance of
2190 * directives.
2191 * @description
2192 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2193 */
2194 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2195
2196 /**
2197 * @ngdoc method
2198 * @name angular.Module#config
2199 * @module ng
2200 * @param {Function} configFn Execute this function on module load. Useful for service
2201 * configuration.
2202 * @description
2203 * Use this method to register work which needs to be performed on module loading.
2204 * For more about how to configure services, see
2205 * {@link providers#provider-recipe Provider Recipe}.
2206 */
2207 config: config,
2208
2209 /**
2210 * @ngdoc method
2211 * @name angular.Module#run
2212 * @module ng
2213 * @param {Function} initializationFn Execute this function after injector creation.
2214 * Useful for application initialization.
2215 * @description
2216 * Use this method to register work which should be performed when the injector is done
2217 * loading all modules.
2218 */
2219 run: function(block) {
2220 runBlocks.push(block);
2221 return this;
2222 }
2223 };
2224
2225 if (configFn) {
2226 config(configFn);
2227 }
2228
2229 return moduleInstance;
2230
2231 /**
2232 * @param {string} provider
2233 * @param {string} method
2234 * @param {String=} insertMethod
2235 * @returns {angular.Module}
2236 */
2237 function invokeLater(provider, method, insertMethod, queue) {
2238 if (!queue) queue = invokeQueue;
2239 return function() {
2240 queue[insertMethod || 'push']([provider, method, arguments]);
2241 return moduleInstance;
2242 };
2243 }
2244
2245 /**
2246 * @param {string} provider
2247 * @param {string} method
2248 * @returns {angular.Module}
2249 */
2250 function invokeLaterAndSetModuleName(provider, method) {
2251 return function(recipeName, factoryFunction) {
2252 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2253 invokeQueue.push([provider, method, arguments]);
2254 return moduleInstance;
2255 };
2256 }
2257 });
2258 };
2259 });
2260
2261}
2262
2263/* global: toDebugString: true */
2264
2265function serializeObject(obj) {
2266 var seen = [];
2267
2268 return JSON.stringify(obj, function(key, val) {
2269 val = toJsonReplacer(key, val);
2270 if (isObject(val)) {
2271
2272 if (seen.indexOf(val) >= 0) return '...';
2273
2274 seen.push(val);
2275 }
2276 return val;
2277 });
2278}
2279
2280function toDebugString(obj) {
2281 if (typeof obj === 'function') {
2282 return obj.toString().replace(/ \{[\s\S]*$/, '');
2283 } else if (isUndefined(obj)) {
2284 return 'undefined';
2285 } else if (typeof obj !== 'string') {
2286 return serializeObject(obj);
2287 }
2288 return obj;
2289}
2290
2291/* global angularModule: true,
2292 version: true,
2293
2294 $CompileProvider,
2295
2296 htmlAnchorDirective,
2297 inputDirective,
2298 inputDirective,
2299 formDirective,
2300 scriptDirective,
2301 selectDirective,
2302 styleDirective,
2303 optionDirective,
2304 ngBindDirective,
2305 ngBindHtmlDirective,
2306 ngBindTemplateDirective,
2307 ngClassDirective,
2308 ngClassEvenDirective,
2309 ngClassOddDirective,
2310 ngCloakDirective,
2311 ngControllerDirective,
2312 ngFormDirective,
2313 ngHideDirective,
2314 ngIfDirective,
2315 ngIncludeDirective,
2316 ngIncludeFillContentDirective,
2317 ngInitDirective,
2318 ngNonBindableDirective,
2319 ngPluralizeDirective,
2320 ngRepeatDirective,
2321 ngShowDirective,
2322 ngStyleDirective,
2323 ngSwitchDirective,
2324 ngSwitchWhenDirective,
2325 ngSwitchDefaultDirective,
2326 ngOptionsDirective,
2327 ngTranscludeDirective,
2328 ngModelDirective,
2329 ngListDirective,
2330 ngChangeDirective,
2331 patternDirective,
2332 patternDirective,
2333 requiredDirective,
2334 requiredDirective,
2335 minlengthDirective,
2336 minlengthDirective,
2337 maxlengthDirective,
2338 maxlengthDirective,
2339 ngValueDirective,
2340 ngModelOptionsDirective,
2341 ngAttributeAliasDirectives,
2342 ngEventDirectives,
2343
2344 $AnchorScrollProvider,
2345 $AnimateProvider,
2346 $CoreAnimateCssProvider,
2347 $$CoreAnimateQueueProvider,
2348 $$CoreAnimateRunnerProvider,
2349 $BrowserProvider,
2350 $CacheFactoryProvider,
2351 $ControllerProvider,
2352 $DocumentProvider,
2353 $ExceptionHandlerProvider,
2354 $FilterProvider,
2355 $$ForceReflowProvider,
2356 $InterpolateProvider,
2357 $IntervalProvider,
2358 $$HashMapProvider,
2359 $HttpProvider,
2360 $HttpParamSerializerProvider,
2361 $HttpParamSerializerJQLikeProvider,
2362 $HttpBackendProvider,
2363 $xhrFactoryProvider,
2364 $LocationProvider,
2365 $LogProvider,
2366 $ParseProvider,
2367 $RootScopeProvider,
2368 $QProvider,
2369 $$QProvider,
2370 $$SanitizeUriProvider,
2371 $SceProvider,
2372 $SceDelegateProvider,
2373 $SnifferProvider,
2374 $TemplateCacheProvider,
2375 $TemplateRequestProvider,
2376 $$TestabilityProvider,
2377 $TimeoutProvider,
2378 $$RAFProvider,
2379 $WindowProvider,
2380 $$jqLiteProvider,
2381 $$CookieReaderProvider
2382*/
2383
2384
2385/**
2386 * @ngdoc object
2387 * @name angular.version
2388 * @module ng
2389 * @description
2390 * An object that contains information about the current AngularJS version.
2391 *
2392 * This object has the following properties:
2393 *
2394 * - `full` – `{string}` – Full version string, such as "0.9.18".
2395 * - `major` – `{number}` – Major version number, such as "0".
2396 * - `minor` – `{number}` – Minor version number, such as "9".
2397 * - `dot` – `{number}` – Dot version number, such as "18".
2398 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2399 */
2400var version = {
2401 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2402 major: 1, // package task
2403 minor: 4,
2404 dot: 8,
2405 codeName: 'ice-manipulation'
2406};
2407
2408
2409function publishExternalAPI(angular) {
2410 extend(angular, {
2411 'bootstrap': bootstrap,
2412 'copy': copy,
2413 'extend': extend,
2414 'merge': merge,
2415 'equals': equals,
2416 'element': jqLite,
2417 'forEach': forEach,
2418 'injector': createInjector,
2419 'noop': noop,
2420 'bind': bind,
2421 'toJson': toJson,
2422 'fromJson': fromJson,
2423 'identity': identity,
2424 'isUndefined': isUndefined,
2425 'isDefined': isDefined,
2426 'isString': isString,
2427 'isFunction': isFunction,
2428 'isObject': isObject,
2429 'isNumber': isNumber,
2430 'isElement': isElement,
2431 'isArray': isArray,
2432 'version': version,
2433 'isDate': isDate,
2434 'lowercase': lowercase,
2435 'uppercase': uppercase,
2436 'callbacks': {counter: 0},
2437 'getTestability': getTestability,
2438 '$$minErr': minErr,
2439 '$$csp': csp,
2440 'reloadWithDebugInfo': reloadWithDebugInfo
2441 });
2442
2443 angularModule = setupModuleLoader(window);
2444
2445 angularModule('ng', ['ngLocale'], ['$provide',
2446 function ngModule($provide) {
2447 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2448 $provide.provider({
2449 $$sanitizeUri: $$SanitizeUriProvider
2450 });
2451 $provide.provider('$compile', $CompileProvider).
2452 directive({
2453 a: htmlAnchorDirective,
2454 input: inputDirective,
2455 textarea: inputDirective,
2456 form: formDirective,
2457 script: scriptDirective,
2458 select: selectDirective,
2459 style: styleDirective,
2460 option: optionDirective,
2461 ngBind: ngBindDirective,
2462 ngBindHtml: ngBindHtmlDirective,
2463 ngBindTemplate: ngBindTemplateDirective,
2464 ngClass: ngClassDirective,
2465 ngClassEven: ngClassEvenDirective,
2466 ngClassOdd: ngClassOddDirective,
2467 ngCloak: ngCloakDirective,
2468 ngController: ngControllerDirective,
2469 ngForm: ngFormDirective,
2470 ngHide: ngHideDirective,
2471 ngIf: ngIfDirective,
2472 ngInclude: ngIncludeDirective,
2473 ngInit: ngInitDirective,
2474 ngNonBindable: ngNonBindableDirective,
2475 ngPluralize: ngPluralizeDirective,
2476 ngRepeat: ngRepeatDirective,
2477 ngShow: ngShowDirective,
2478 ngStyle: ngStyleDirective,
2479 ngSwitch: ngSwitchDirective,
2480 ngSwitchWhen: ngSwitchWhenDirective,
2481 ngSwitchDefault: ngSwitchDefaultDirective,
2482 ngOptions: ngOptionsDirective,
2483 ngTransclude: ngTranscludeDirective,
2484 ngModel: ngModelDirective,
2485 ngList: ngListDirective,
2486 ngChange: ngChangeDirective,
2487 pattern: patternDirective,
2488 ngPattern: patternDirective,
2489 required: requiredDirective,
2490 ngRequired: requiredDirective,
2491 minlength: minlengthDirective,
2492 ngMinlength: minlengthDirective,
2493 maxlength: maxlengthDirective,
2494 ngMaxlength: maxlengthDirective,
2495 ngValue: ngValueDirective,
2496 ngModelOptions: ngModelOptionsDirective
2497 }).
2498 directive({
2499 ngInclude: ngIncludeFillContentDirective
2500 }).
2501 directive(ngAttributeAliasDirectives).
2502 directive(ngEventDirectives);
2503 $provide.provider({
2504 $anchorScroll: $AnchorScrollProvider,
2505 $animate: $AnimateProvider,
2506 $animateCss: $CoreAnimateCssProvider,
2507 $$animateQueue: $$CoreAnimateQueueProvider,
2508 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2509 $browser: $BrowserProvider,
2510 $cacheFactory: $CacheFactoryProvider,
2511 $controller: $ControllerProvider,
2512 $document: $DocumentProvider,
2513 $exceptionHandler: $ExceptionHandlerProvider,
2514 $filter: $FilterProvider,
2515 $$forceReflow: $$ForceReflowProvider,
2516 $interpolate: $InterpolateProvider,
2517 $interval: $IntervalProvider,
2518 $http: $HttpProvider,
2519 $httpParamSerializer: $HttpParamSerializerProvider,
2520 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2521 $httpBackend: $HttpBackendProvider,
2522 $xhrFactory: $xhrFactoryProvider,
2523 $location: $LocationProvider,
2524 $log: $LogProvider,
2525 $parse: $ParseProvider,
2526 $rootScope: $RootScopeProvider,
2527 $q: $QProvider,
2528 $$q: $$QProvider,
2529 $sce: $SceProvider,
2530 $sceDelegate: $SceDelegateProvider,
2531 $sniffer: $SnifferProvider,
2532 $templateCache: $TemplateCacheProvider,
2533 $templateRequest: $TemplateRequestProvider,
2534 $$testability: $$TestabilityProvider,
2535 $timeout: $TimeoutProvider,
2536 $window: $WindowProvider,
2537 $$rAF: $$RAFProvider,
2538 $$jqLite: $$jqLiteProvider,
2539 $$HashMap: $$HashMapProvider,
2540 $$cookieReader: $$CookieReaderProvider
2541 });
2542 }
2543 ]);
2544}
2545
2546/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2547 * Any commits to this file should be reviewed with security in mind. *
2548 * Changes to this file can potentially create security vulnerabilities. *
2549 * An approval from 2 Core members with history of modifying *
2550 * this file is required. *
2551 * *
2552 * Does the change somehow allow for arbitrary javascript to be executed? *
2553 * Or allows for someone to change the prototype of built-in objects? *
2554 * Or gives undesired access to variables likes document or window? *
2555 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2556
2557/* global JQLitePrototype: true,
2558 addEventListenerFn: true,
2559 removeEventListenerFn: true,
2560 BOOLEAN_ATTR: true,
2561 ALIASED_ATTR: true,
2562*/
2563
2564//////////////////////////////////
2565//JQLite
2566//////////////////////////////////
2567
2568/**
2569 * @ngdoc function
2570 * @name angular.element
2571 * @module ng
2572 * @kind function
2573 *
2574 * @description
2575 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2576 *
2577 * If jQuery is available, `angular.element` is an alias for the
2578 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2579 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2580 *
2581 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2582 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2583 * commonly needed functionality with the goal of having a very small footprint.</div>
2584 *
2585 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2586 *
2587 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2588 * jqLite; they are never raw DOM references.</div>
2589 *
2590 * ## Angular's jqLite
2591 * jqLite provides only the following jQuery methods:
2592 *
2593 * - [`addClass()`](http://api.jquery.com/addClass/)
2594 * - [`after()`](http://api.jquery.com/after/)
2595 * - [`append()`](http://api.jquery.com/append/)
2596 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2597 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2598 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2599 * - [`clone()`](http://api.jquery.com/clone/)
2600 * - [`contents()`](http://api.jquery.com/contents/)
2601 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
2602 * - [`data()`](http://api.jquery.com/data/)
2603 * - [`detach()`](http://api.jquery.com/detach/)
2604 * - [`empty()`](http://api.jquery.com/empty/)
2605 * - [`eq()`](http://api.jquery.com/eq/)
2606 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2607 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2608 * - [`html()`](http://api.jquery.com/html/)
2609 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2610 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2611 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2612 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2613 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2614 * - [`prepend()`](http://api.jquery.com/prepend/)
2615 * - [`prop()`](http://api.jquery.com/prop/)
2616 * - [`ready()`](http://api.jquery.com/ready/)
2617 * - [`remove()`](http://api.jquery.com/remove/)
2618 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2619 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2620 * - [`removeData()`](http://api.jquery.com/removeData/)
2621 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2622 * - [`text()`](http://api.jquery.com/text/)
2623 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2624 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2625 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2626 * - [`val()`](http://api.jquery.com/val/)
2627 * - [`wrap()`](http://api.jquery.com/wrap/)
2628 *
2629 * ## jQuery/jqLite Extras
2630 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2631 *
2632 * ### Events
2633 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2634 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2635 * element before it is removed.
2636 *
2637 * ### Methods
2638 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2639 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2640 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2641 * `'ngModel'`).
2642 * - `injector()` - retrieves the injector of the current element or its parent.
2643 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2644 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2645 * be enabled.
2646 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2647 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2648 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2649 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2650 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2651 * parent element is reached.
2652 *
2653 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2654 * @returns {Object} jQuery object.
2655 */
2656
2657JQLite.expando = 'ng339';
2658
2659var jqCache = JQLite.cache = {},
2660 jqId = 1,
2661 addEventListenerFn = function(element, type, fn) {
2662 element.addEventListener(type, fn, false);
2663 },
2664 removeEventListenerFn = function(element, type, fn) {
2665 element.removeEventListener(type, fn, false);
2666 };
2667
2668/*
2669 * !!! This is an undocumented "private" function !!!
2670 */
2671JQLite._data = function(node) {
2672 //jQuery always returns an object on cache miss
2673 return this.cache[node[this.expando]] || {};
2674};
2675
2676function jqNextId() { return ++jqId; }
2677
2678
2679var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2680var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2681var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2682var jqLiteMinErr = minErr('jqLite');
2683
2684/**
2685 * Converts snake_case to camelCase.
2686 * Also there is special case for Moz prefix starting with upper case letter.
2687 * @param name Name to normalize
2688 */
2689function camelCase(name) {
2690 return name.
2691 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2692 return offset ? letter.toUpperCase() : letter;
2693 }).
2694 replace(MOZ_HACK_REGEXP, 'Moz$1');
2695}
2696
2697var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2698var HTML_REGEXP = /<|&#?\w+;/;
2699var TAG_NAME_REGEXP = /<([\w:-]+)/;
2700var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2701
2702var wrapMap = {
2703 'option': [1, '<select multiple="multiple">', '</select>'],
2704
2705 'thead': [1, '<table>', '</table>'],
2706 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2707 'tr': [2, '<table><tbody>', '</tbody></table>'],
2708 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2709 '_default': [0, "", ""]
2710};
2711
2712wrapMap.optgroup = wrapMap.option;
2713wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2714wrapMap.th = wrapMap.td;
2715
2716
2717function jqLiteIsTextNode(html) {
2718 return !HTML_REGEXP.test(html);
2719}
2720
2721function jqLiteAcceptsData(node) {
2722 // The window object can accept data but has no nodeType
2723 // Otherwise we are only interested in elements (1) and documents (9)
2724 var nodeType = node.nodeType;
2725 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2726}
2727
2728function jqLiteHasData(node) {
2729 for (var key in jqCache[node.ng339]) {
2730 return true;
2731 }
2732 return false;
2733}
2734
2735function jqLiteBuildFragment(html, context) {
2736 var tmp, tag, wrap,
2737 fragment = context.createDocumentFragment(),
2738 nodes = [], i;
2739
2740 if (jqLiteIsTextNode(html)) {
2741 // Convert non-html into a text node
2742 nodes.push(context.createTextNode(html));
2743 } else {
2744 // Convert html into DOM nodes
2745 tmp = tmp || fragment.appendChild(context.createElement("div"));
2746 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2747 wrap = wrapMap[tag] || wrapMap._default;
2748 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2749
2750 // Descend through wrappers to the right content
2751 i = wrap[0];
2752 while (i--) {
2753 tmp = tmp.lastChild;
2754 }
2755
2756 nodes = concat(nodes, tmp.childNodes);
2757
2758 tmp = fragment.firstChild;
2759 tmp.textContent = "";
2760 }
2761
2762 // Remove wrapper from fragment
2763 fragment.textContent = "";
2764 fragment.innerHTML = ""; // Clear inner HTML
2765 forEach(nodes, function(node) {
2766 fragment.appendChild(node);
2767 });
2768
2769 return fragment;
2770}
2771
2772function jqLiteParseHTML(html, context) {
2773 context = context || document;
2774 var parsed;
2775
2776 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2777 return [context.createElement(parsed[1])];
2778 }
2779
2780 if ((parsed = jqLiteBuildFragment(html, context))) {
2781 return parsed.childNodes;
2782 }
2783
2784 return [];
2785}
2786
2787
2788// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2789var jqLiteContains = Node.prototype.contains || function(arg) {
2790 // jshint bitwise: false
2791 return !!(this.compareDocumentPosition(arg) & 16);
2792 // jshint bitwise: true
2793};
2794
2795/////////////////////////////////////////////
2796function JQLite(element) {
2797 if (element instanceof JQLite) {
2798 return element;
2799 }
2800
2801 var argIsString;
2802
2803 if (isString(element)) {
2804 element = trim(element);
2805 argIsString = true;
2806 }
2807 if (!(this instanceof JQLite)) {
2808 if (argIsString && element.charAt(0) != '<') {
2809 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2810 }
2811 return new JQLite(element);
2812 }
2813
2814 if (argIsString) {
2815 jqLiteAddNodes(this, jqLiteParseHTML(element));
2816 } else {
2817 jqLiteAddNodes(this, element);
2818 }
2819}
2820
2821function jqLiteClone(element) {
2822 return element.cloneNode(true);
2823}
2824
2825function jqLiteDealoc(element, onlyDescendants) {
2826 if (!onlyDescendants) jqLiteRemoveData(element);
2827
2828 if (element.querySelectorAll) {
2829 var descendants = element.querySelectorAll('*');
2830 for (var i = 0, l = descendants.length; i < l; i++) {
2831 jqLiteRemoveData(descendants[i]);
2832 }
2833 }
2834}
2835
2836function jqLiteOff(element, type, fn, unsupported) {
2837 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2838
2839 var expandoStore = jqLiteExpandoStore(element);
2840 var events = expandoStore && expandoStore.events;
2841 var handle = expandoStore && expandoStore.handle;
2842
2843 if (!handle) return; //no listeners registered
2844
2845 if (!type) {
2846 for (type in events) {
2847 if (type !== '$destroy') {
2848 removeEventListenerFn(element, type, handle);
2849 }
2850 delete events[type];
2851 }
2852 } else {
2853
2854 var removeHandler = function(type) {
2855 var listenerFns = events[type];
2856 if (isDefined(fn)) {
2857 arrayRemove(listenerFns || [], fn);
2858 }
2859 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2860 removeEventListenerFn(element, type, handle);
2861 delete events[type];
2862 }
2863 };
2864
2865 forEach(type.split(' '), function(type) {
2866 removeHandler(type);
2867 if (MOUSE_EVENT_MAP[type]) {
2868 removeHandler(MOUSE_EVENT_MAP[type]);
2869 }
2870 });
2871 }
2872}
2873
2874function jqLiteRemoveData(element, name) {
2875 var expandoId = element.ng339;
2876 var expandoStore = expandoId && jqCache[expandoId];
2877
2878 if (expandoStore) {
2879 if (name) {
2880 delete expandoStore.data[name];
2881 return;
2882 }
2883
2884 if (expandoStore.handle) {
2885 if (expandoStore.events.$destroy) {
2886 expandoStore.handle({}, '$destroy');
2887 }
2888 jqLiteOff(element);
2889 }
2890 delete jqCache[expandoId];
2891 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2892 }
2893}
2894
2895
2896function jqLiteExpandoStore(element, createIfNecessary) {
2897 var expandoId = element.ng339,
2898 expandoStore = expandoId && jqCache[expandoId];
2899
2900 if (createIfNecessary && !expandoStore) {
2901 element.ng339 = expandoId = jqNextId();
2902 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2903 }
2904
2905 return expandoStore;
2906}
2907
2908
2909function jqLiteData(element, key, value) {
2910 if (jqLiteAcceptsData(element)) {
2911
2912 var isSimpleSetter = isDefined(value);
2913 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2914 var massGetter = !key;
2915 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2916 var data = expandoStore && expandoStore.data;
2917
2918 if (isSimpleSetter) { // data('key', value)
2919 data[key] = value;
2920 } else {
2921 if (massGetter) { // data()
2922 return data;
2923 } else {
2924 if (isSimpleGetter) { // data('key')
2925 // don't force creation of expandoStore if it doesn't exist yet
2926 return data && data[key];
2927 } else { // mass-setter: data({key1: val1, key2: val2})
2928 extend(data, key);
2929 }
2930 }
2931 }
2932 }
2933}
2934
2935function jqLiteHasClass(element, selector) {
2936 if (!element.getAttribute) return false;
2937 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
2938 indexOf(" " + selector + " ") > -1);
2939}
2940
2941function jqLiteRemoveClass(element, cssClasses) {
2942 if (cssClasses && element.setAttribute) {
2943 forEach(cssClasses.split(' '), function(cssClass) {
2944 element.setAttribute('class', trim(
2945 (" " + (element.getAttribute('class') || '') + " ")
2946 .replace(/[\n\t]/g, " ")
2947 .replace(" " + trim(cssClass) + " ", " "))
2948 );
2949 });
2950 }
2951}
2952
2953function jqLiteAddClass(element, cssClasses) {
2954 if (cssClasses && element.setAttribute) {
2955 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
2956 .replace(/[\n\t]/g, " ");
2957
2958 forEach(cssClasses.split(' '), function(cssClass) {
2959 cssClass = trim(cssClass);
2960 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
2961 existingClasses += cssClass + ' ';
2962 }
2963 });
2964
2965 element.setAttribute('class', trim(existingClasses));
2966 }
2967}
2968
2969
2970function jqLiteAddNodes(root, elements) {
2971 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
2972
2973 if (elements) {
2974
2975 // if a Node (the most common case)
2976 if (elements.nodeType) {
2977 root[root.length++] = elements;
2978 } else {
2979 var length = elements.length;
2980
2981 // if an Array or NodeList and not a Window
2982 if (typeof length === 'number' && elements.window !== elements) {
2983 if (length) {
2984 for (var i = 0; i < length; i++) {
2985 root[root.length++] = elements[i];
2986 }
2987 }
2988 } else {
2989 root[root.length++] = elements;
2990 }
2991 }
2992 }
2993}
2994
2995
2996function jqLiteController(element, name) {
2997 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
2998}
2999
3000function jqLiteInheritedData(element, name, value) {
3001 // if element is the document object work with the html element instead
3002 // this makes $(document).scope() possible
3003 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3004 element = element.documentElement;
3005 }
3006 var names = isArray(name) ? name : [name];
3007
3008 while (element) {
3009 for (var i = 0, ii = names.length; i < ii; i++) {
3010 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3011 }
3012
3013 // If dealing with a document fragment node with a host element, and no parent, use the host
3014 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3015 // to lookup parent controllers.
3016 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3017 }
3018}
3019
3020function jqLiteEmpty(element) {
3021 jqLiteDealoc(element, true);
3022 while (element.firstChild) {
3023 element.removeChild(element.firstChild);
3024 }
3025}
3026
3027function jqLiteRemove(element, keepData) {
3028 if (!keepData) jqLiteDealoc(element);
3029 var parent = element.parentNode;
3030 if (parent) parent.removeChild(element);
3031}
3032
3033
3034function jqLiteDocumentLoaded(action, win) {
3035 win = win || window;
3036 if (win.document.readyState === 'complete') {
3037 // Force the action to be run async for consistent behaviour
3038 // from the action's point of view
3039 // i.e. it will definitely not be in a $apply
3040 win.setTimeout(action);
3041 } else {
3042 // No need to unbind this handler as load is only ever called once
3043 jqLite(win).on('load', action);
3044 }
3045}
3046
3047//////////////////////////////////////////
3048// Functions which are declared directly.
3049//////////////////////////////////////////
3050var JQLitePrototype = JQLite.prototype = {
3051 ready: function(fn) {
3052 var fired = false;
3053
3054 function trigger() {
3055 if (fired) return;
3056 fired = true;
3057 fn();
3058 }
3059
3060 // check if document is already loaded
3061 if (document.readyState === 'complete') {
3062 setTimeout(trigger);
3063 } else {
3064 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3065 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3066 // jshint -W064
3067 JQLite(window).on('load', trigger); // fallback to window.onload for others
3068 // jshint +W064
3069 }
3070 },
3071 toString: function() {
3072 var value = [];
3073 forEach(this, function(e) { value.push('' + e);});
3074 return '[' + value.join(', ') + ']';
3075 },
3076
3077 eq: function(index) {
3078 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3079 },
3080
3081 length: 0,
3082 push: push,
3083 sort: [].sort,
3084 splice: [].splice
3085};
3086
3087//////////////////////////////////////////
3088// Functions iterating getter/setters.
3089// these functions return self on setter and
3090// value on get.
3091//////////////////////////////////////////
3092var BOOLEAN_ATTR = {};
3093forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3094 BOOLEAN_ATTR[lowercase(value)] = value;
3095});
3096var BOOLEAN_ELEMENTS = {};
3097forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3098 BOOLEAN_ELEMENTS[value] = true;
3099});
3100var ALIASED_ATTR = {
3101 'ngMinlength': 'minlength',
3102 'ngMaxlength': 'maxlength',
3103 'ngMin': 'min',
3104 'ngMax': 'max',
3105 'ngPattern': 'pattern'
3106};
3107
3108function getBooleanAttrName(element, name) {
3109 // check dom last since we will most likely fail on name
3110 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3111
3112 // booleanAttr is here twice to minimize DOM access
3113 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3114}
3115
3116function getAliasedAttrName(name) {
3117 return ALIASED_ATTR[name];
3118}
3119
3120forEach({
3121 data: jqLiteData,
3122 removeData: jqLiteRemoveData,
3123 hasData: jqLiteHasData
3124}, function(fn, name) {
3125 JQLite[name] = fn;
3126});
3127
3128forEach({
3129 data: jqLiteData,
3130 inheritedData: jqLiteInheritedData,
3131
3132 scope: function(element) {
3133 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3134 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3135 },
3136
3137 isolateScope: function(element) {
3138 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3139 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3140 },
3141
3142 controller: jqLiteController,
3143
3144 injector: function(element) {
3145 return jqLiteInheritedData(element, '$injector');
3146 },
3147
3148 removeAttr: function(element, name) {
3149 element.removeAttribute(name);
3150 },
3151
3152 hasClass: jqLiteHasClass,
3153
3154 css: function(element, name, value) {
3155 name = camelCase(name);
3156
3157 if (isDefined(value)) {
3158 element.style[name] = value;
3159 } else {
3160 return element.style[name];
3161 }
3162 },
3163
3164 attr: function(element, name, value) {
3165 var nodeType = element.nodeType;
3166 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3167 return;
3168 }
3169 var lowercasedName = lowercase(name);
3170 if (BOOLEAN_ATTR[lowercasedName]) {
3171 if (isDefined(value)) {
3172 if (!!value) {
3173 element[name] = true;
3174 element.setAttribute(name, lowercasedName);
3175 } else {
3176 element[name] = false;
3177 element.removeAttribute(lowercasedName);
3178 }
3179 } else {
3180 return (element[name] ||
3181 (element.attributes.getNamedItem(name) || noop).specified)
3182 ? lowercasedName
3183 : undefined;
3184 }
3185 } else if (isDefined(value)) {
3186 element.setAttribute(name, value);
3187 } else if (element.getAttribute) {
3188 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3189 // some elements (e.g. Document) don't have get attribute, so return undefined
3190 var ret = element.getAttribute(name, 2);
3191 // normalize non-existing attributes to undefined (as jQuery)
3192 return ret === null ? undefined : ret;
3193 }
3194 },
3195
3196 prop: function(element, name, value) {
3197 if (isDefined(value)) {
3198 element[name] = value;
3199 } else {
3200 return element[name];
3201 }
3202 },
3203
3204 text: (function() {
3205 getText.$dv = '';
3206 return getText;
3207
3208 function getText(element, value) {
3209 if (isUndefined(value)) {
3210 var nodeType = element.nodeType;
3211 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3212 }
3213 element.textContent = value;
3214 }
3215 })(),
3216
3217 val: function(element, value) {
3218 if (isUndefined(value)) {
3219 if (element.multiple && nodeName_(element) === 'select') {
3220 var result = [];
3221 forEach(element.options, function(option) {
3222 if (option.selected) {
3223 result.push(option.value || option.text);
3224 }
3225 });
3226 return result.length === 0 ? null : result;
3227 }
3228 return element.value;
3229 }
3230 element.value = value;
3231 },
3232
3233 html: function(element, value) {
3234 if (isUndefined(value)) {
3235 return element.innerHTML;
3236 }
3237 jqLiteDealoc(element, true);
3238 element.innerHTML = value;
3239 },
3240
3241 empty: jqLiteEmpty
3242}, function(fn, name) {
3243 /**
3244 * Properties: writes return selection, reads return first value
3245 */
3246 JQLite.prototype[name] = function(arg1, arg2) {
3247 var i, key;
3248 var nodeCount = this.length;
3249
3250 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3251 // in a way that survives minification.
3252 // jqLiteEmpty takes no arguments but is a setter.
3253 if (fn !== jqLiteEmpty &&
3254 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3255 if (isObject(arg1)) {
3256
3257 // we are a write, but the object properties are the key/values
3258 for (i = 0; i < nodeCount; i++) {
3259 if (fn === jqLiteData) {
3260 // data() takes the whole object in jQuery
3261 fn(this[i], arg1);
3262 } else {
3263 for (key in arg1) {
3264 fn(this[i], key, arg1[key]);
3265 }
3266 }
3267 }
3268 // return self for chaining
3269 return this;
3270 } else {
3271 // we are a read, so read the first child.
3272 // TODO: do we still need this?
3273 var value = fn.$dv;
3274 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3275 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3276 for (var j = 0; j < jj; j++) {
3277 var nodeValue = fn(this[j], arg1, arg2);
3278 value = value ? value + nodeValue : nodeValue;
3279 }
3280 return value;
3281 }
3282 } else {
3283 // we are a write, so apply to all children
3284 for (i = 0; i < nodeCount; i++) {
3285 fn(this[i], arg1, arg2);
3286 }
3287 // return self for chaining
3288 return this;
3289 }
3290 };
3291});
3292
3293function createEventHandler(element, events) {
3294 var eventHandler = function(event, type) {
3295 // jQuery specific api
3296 event.isDefaultPrevented = function() {
3297 return event.defaultPrevented;
3298 };
3299
3300 var eventFns = events[type || event.type];
3301 var eventFnsLength = eventFns ? eventFns.length : 0;
3302
3303 if (!eventFnsLength) return;
3304
3305 if (isUndefined(event.immediatePropagationStopped)) {
3306 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3307 event.stopImmediatePropagation = function() {
3308 event.immediatePropagationStopped = true;
3309
3310 if (event.stopPropagation) {
3311 event.stopPropagation();
3312 }
3313
3314 if (originalStopImmediatePropagation) {
3315 originalStopImmediatePropagation.call(event);
3316 }
3317 };
3318 }
3319
3320 event.isImmediatePropagationStopped = function() {
3321 return event.immediatePropagationStopped === true;
3322 };
3323
3324 // Some events have special handlers that wrap the real handler
3325 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3326
3327 // Copy event handlers in case event handlers array is modified during execution.
3328 if ((eventFnsLength > 1)) {
3329 eventFns = shallowCopy(eventFns);
3330 }
3331
3332 for (var i = 0; i < eventFnsLength; i++) {
3333 if (!event.isImmediatePropagationStopped()) {
3334 handlerWrapper(element, event, eventFns[i]);
3335 }
3336 }
3337 };
3338
3339 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3340 // events on `element`
3341 eventHandler.elem = element;
3342 return eventHandler;
3343}
3344
3345function defaultHandlerWrapper(element, event, handler) {
3346 handler.call(element, event);
3347}
3348
3349function specialMouseHandlerWrapper(target, event, handler) {
3350 // Refer to jQuery's implementation of mouseenter & mouseleave
3351 // Read about mouseenter and mouseleave:
3352 // http://www.quirksmode.org/js/events_mouse.html#link8
3353 var related = event.relatedTarget;
3354 // For mousenter/leave call the handler if related is outside the target.
3355 // NB: No relatedTarget if the mouse left/entered the browser window
3356 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3357 handler.call(target, event);
3358 }
3359}
3360
3361//////////////////////////////////////////
3362// Functions iterating traversal.
3363// These functions chain results into a single
3364// selector.
3365//////////////////////////////////////////
3366forEach({
3367 removeData: jqLiteRemoveData,
3368
3369 on: function jqLiteOn(element, type, fn, unsupported) {
3370 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3371
3372 // Do not add event handlers to non-elements because they will not be cleaned up.
3373 if (!jqLiteAcceptsData(element)) {
3374 return;
3375 }
3376
3377 var expandoStore = jqLiteExpandoStore(element, true);
3378 var events = expandoStore.events;
3379 var handle = expandoStore.handle;
3380
3381 if (!handle) {
3382 handle = expandoStore.handle = createEventHandler(element, events);
3383 }
3384
3385 // http://jsperf.com/string-indexof-vs-split
3386 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3387 var i = types.length;
3388
3389 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3390 var eventFns = events[type];
3391
3392 if (!eventFns) {
3393 eventFns = events[type] = [];
3394 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3395 if (type !== '$destroy' && !noEventListener) {
3396 addEventListenerFn(element, type, handle);
3397 }
3398 }
3399
3400 eventFns.push(fn);
3401 };
3402
3403 while (i--) {
3404 type = types[i];
3405 if (MOUSE_EVENT_MAP[type]) {
3406 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3407 addHandler(type, undefined, true);
3408 } else {
3409 addHandler(type);
3410 }
3411 }
3412 },
3413
3414 off: jqLiteOff,
3415
3416 one: function(element, type, fn) {
3417 element = jqLite(element);
3418
3419 //add the listener twice so that when it is called
3420 //you can remove the original function and still be
3421 //able to call element.off(ev, fn) normally
3422 element.on(type, function onFn() {
3423 element.off(type, fn);
3424 element.off(type, onFn);
3425 });
3426 element.on(type, fn);
3427 },
3428
3429 replaceWith: function(element, replaceNode) {
3430 var index, parent = element.parentNode;
3431 jqLiteDealoc(element);
3432 forEach(new JQLite(replaceNode), function(node) {
3433 if (index) {
3434 parent.insertBefore(node, index.nextSibling);
3435 } else {
3436 parent.replaceChild(node, element);
3437 }
3438 index = node;
3439 });
3440 },
3441
3442 children: function(element) {
3443 var children = [];
3444 forEach(element.childNodes, function(element) {
3445 if (element.nodeType === NODE_TYPE_ELEMENT) {
3446 children.push(element);
3447 }
3448 });
3449 return children;
3450 },
3451
3452 contents: function(element) {
3453 return element.contentDocument || element.childNodes || [];
3454 },
3455
3456 append: function(element, node) {
3457 var nodeType = element.nodeType;
3458 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3459
3460 node = new JQLite(node);
3461
3462 for (var i = 0, ii = node.length; i < ii; i++) {
3463 var child = node[i];
3464 element.appendChild(child);
3465 }
3466 },
3467
3468 prepend: function(element, node) {
3469 if (element.nodeType === NODE_TYPE_ELEMENT) {
3470 var index = element.firstChild;
3471 forEach(new JQLite(node), function(child) {
3472 element.insertBefore(child, index);
3473 });
3474 }
3475 },
3476
3477 wrap: function(element, wrapNode) {
3478 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3479 var parent = element.parentNode;
3480 if (parent) {
3481 parent.replaceChild(wrapNode, element);
3482 }
3483 wrapNode.appendChild(element);
3484 },
3485
3486 remove: jqLiteRemove,
3487
3488 detach: function(element) {
3489 jqLiteRemove(element, true);
3490 },
3491
3492 after: function(element, newElement) {
3493 var index = element, parent = element.parentNode;
3494 newElement = new JQLite(newElement);
3495
3496 for (var i = 0, ii = newElement.length; i < ii; i++) {
3497 var node = newElement[i];
3498 parent.insertBefore(node, index.nextSibling);
3499 index = node;
3500 }
3501 },
3502
3503 addClass: jqLiteAddClass,
3504 removeClass: jqLiteRemoveClass,
3505
3506 toggleClass: function(element, selector, condition) {
3507 if (selector) {
3508 forEach(selector.split(' '), function(className) {
3509 var classCondition = condition;
3510 if (isUndefined(classCondition)) {
3511 classCondition = !jqLiteHasClass(element, className);
3512 }
3513 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3514 });
3515 }
3516 },
3517
3518 parent: function(element) {
3519 var parent = element.parentNode;
3520 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3521 },
3522
3523 next: function(element) {
3524 return element.nextElementSibling;
3525 },
3526
3527 find: function(element, selector) {
3528 if (element.getElementsByTagName) {
3529 return element.getElementsByTagName(selector);
3530 } else {
3531 return [];
3532 }
3533 },
3534
3535 clone: jqLiteClone,
3536
3537 triggerHandler: function(element, event, extraParameters) {
3538
3539 var dummyEvent, eventFnsCopy, handlerArgs;
3540 var eventName = event.type || event;
3541 var expandoStore = jqLiteExpandoStore(element);
3542 var events = expandoStore && expandoStore.events;
3543 var eventFns = events && events[eventName];
3544
3545 if (eventFns) {
3546 // Create a dummy event to pass to the handlers
3547 dummyEvent = {
3548 preventDefault: function() { this.defaultPrevented = true; },
3549 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3550 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3551 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3552 stopPropagation: noop,
3553 type: eventName,
3554 target: element
3555 };
3556
3557 // If a custom event was provided then extend our dummy event with it
3558 if (event.type) {
3559 dummyEvent = extend(dummyEvent, event);
3560 }
3561
3562 // Copy event handlers in case event handlers array is modified during execution.
3563 eventFnsCopy = shallowCopy(eventFns);
3564 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3565
3566 forEach(eventFnsCopy, function(fn) {
3567 if (!dummyEvent.isImmediatePropagationStopped()) {
3568 fn.apply(element, handlerArgs);
3569 }
3570 });
3571 }
3572 }
3573}, function(fn, name) {
3574 /**
3575 * chaining functions
3576 */
3577 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3578 var value;
3579
3580 for (var i = 0, ii = this.length; i < ii; i++) {
3581 if (isUndefined(value)) {
3582 value = fn(this[i], arg1, arg2, arg3);
3583 if (isDefined(value)) {
3584 // any function which returns a value needs to be wrapped
3585 value = jqLite(value);
3586 }
3587 } else {
3588 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3589 }
3590 }
3591 return isDefined(value) ? value : this;
3592 };
3593
3594 // bind legacy bind/unbind to on/off
3595 JQLite.prototype.bind = JQLite.prototype.on;
3596 JQLite.prototype.unbind = JQLite.prototype.off;
3597});
3598
3599
3600// Provider for private $$jqLite service
3601function $$jqLiteProvider() {
3602 this.$get = function $$jqLite() {
3603 return extend(JQLite, {
3604 hasClass: function(node, classes) {
3605 if (node.attr) node = node[0];
3606 return jqLiteHasClass(node, classes);
3607 },
3608 addClass: function(node, classes) {
3609 if (node.attr) node = node[0];
3610 return jqLiteAddClass(node, classes);
3611 },
3612 removeClass: function(node, classes) {
3613 if (node.attr) node = node[0];
3614 return jqLiteRemoveClass(node, classes);
3615 }
3616 });
3617 };
3618}
3619
3620/**
3621 * Computes a hash of an 'obj'.
3622 * Hash of a:
3623 * string is string
3624 * number is number as string
3625 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3626 * that is also assigned to the $$hashKey property of the object.
3627 *
3628 * @param obj
3629 * @returns {string} hash string such that the same input will have the same hash string.
3630 * The resulting string key is in 'type:hashKey' format.
3631 */
3632function hashKey(obj, nextUidFn) {
3633 var key = obj && obj.$$hashKey;
3634
3635 if (key) {
3636 if (typeof key === 'function') {
3637 key = obj.$$hashKey();
3638 }
3639 return key;
3640 }
3641
3642 var objType = typeof obj;
3643 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3644 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3645 } else {
3646 key = objType + ':' + obj;
3647 }
3648
3649 return key;
3650}
3651
3652/**
3653 * HashMap which can use objects as keys
3654 */
3655function HashMap(array, isolatedUid) {
3656 if (isolatedUid) {
3657 var uid = 0;
3658 this.nextUid = function() {
3659 return ++uid;
3660 };
3661 }
3662 forEach(array, this.put, this);
3663}
3664HashMap.prototype = {
3665 /**
3666 * Store key value pair
3667 * @param key key to store can be any type
3668 * @param value value to store can be any type
3669 */
3670 put: function(key, value) {
3671 this[hashKey(key, this.nextUid)] = value;
3672 },
3673
3674 /**
3675 * @param key
3676 * @returns {Object} the value for the key
3677 */
3678 get: function(key) {
3679 return this[hashKey(key, this.nextUid)];
3680 },
3681
3682 /**
3683 * Remove the key/value pair
3684 * @param key
3685 */
3686 remove: function(key) {
3687 var value = this[key = hashKey(key, this.nextUid)];
3688 delete this[key];
3689 return value;
3690 }
3691};
3692
3693var $$HashMapProvider = [function() {
3694 this.$get = [function() {
3695 return HashMap;
3696 }];
3697}];
3698
3699/**
3700 * @ngdoc function
3701 * @module ng
3702 * @name angular.injector
3703 * @kind function
3704 *
3705 * @description
3706 * Creates an injector object that can be used for retrieving services as well as for
3707 * dependency injection (see {@link guide/di dependency injection}).
3708 *
3709 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3710 * {@link angular.module}. The `ng` module must be explicitly added.
3711 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3712 * disallows argument name annotation inference.
3713 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3714 *
3715 * @example
3716 * Typical usage
3717 * ```js
3718 * // create an injector
3719 * var $injector = angular.injector(['ng']);
3720 *
3721 * // use the injector to kick off your application
3722 * // use the type inference to auto inject arguments, or use implicit injection
3723 * $injector.invoke(function($rootScope, $compile, $document) {
3724 * $compile($document)($rootScope);
3725 * $rootScope.$digest();
3726 * });
3727 * ```
3728 *
3729 * Sometimes you want to get access to the injector of a currently running Angular app
3730 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3731 * application has been bootstrapped. You can do this using the extra `injector()` added
3732 * to JQuery/jqLite elements. See {@link angular.element}.
3733 *
3734 * *This is fairly rare but could be the case if a third party library is injecting the
3735 * markup.*
3736 *
3737 * In the following example a new block of HTML containing a `ng-controller`
3738 * directive is added to the end of the document body by JQuery. We then compile and link
3739 * it into the current AngularJS scope.
3740 *
3741 * ```js
3742 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3743 * $(document.body).append($div);
3744 *
3745 * angular.element(document).injector().invoke(function($compile) {
3746 * var scope = angular.element($div).scope();
3747 * $compile($div)(scope);
3748 * });
3749 * ```
3750 */
3751
3752
3753/**
3754 * @ngdoc module
3755 * @name auto
3756 * @description
3757 *
3758 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3759 */
3760
3761var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3762var FN_ARG_SPLIT = /,/;
3763var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3764var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3765var $injectorMinErr = minErr('$injector');
3766
3767function anonFn(fn) {
3768 // For anonymous functions, showing at the very least the function signature can help in
3769 // debugging.
3770 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3771 args = fnText.match(FN_ARGS);
3772 if (args) {
3773 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3774 }
3775 return 'fn';
3776}
3777
3778function annotate(fn, strictDi, name) {
3779 var $inject,
3780 fnText,
3781 argDecl,
3782 last;
3783
3784 if (typeof fn === 'function') {
3785 if (!($inject = fn.$inject)) {
3786 $inject = [];
3787 if (fn.length) {
3788 if (strictDi) {
3789 if (!isString(name) || !name) {
3790 name = fn.name || anonFn(fn);
3791 }
3792 throw $injectorMinErr('strictdi',
3793 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3794 }
3795 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3796 argDecl = fnText.match(FN_ARGS);
3797 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3798 arg.replace(FN_ARG, function(all, underscore, name) {
3799 $inject.push(name);
3800 });
3801 });
3802 }
3803 fn.$inject = $inject;
3804 }
3805 } else if (isArray(fn)) {
3806 last = fn.length - 1;
3807 assertArgFn(fn[last], 'fn');
3808 $inject = fn.slice(0, last);
3809 } else {
3810 assertArgFn(fn, 'fn', true);
3811 }
3812 return $inject;
3813}
3814
3815///////////////////////////////////////
3816
3817/**
3818 * @ngdoc service
3819 * @name $injector
3820 *
3821 * @description
3822 *
3823 * `$injector` is used to retrieve object instances as defined by
3824 * {@link auto.$provide provider}, instantiate types, invoke methods,
3825 * and load modules.
3826 *
3827 * The following always holds true:
3828 *
3829 * ```js
3830 * var $injector = angular.injector();
3831 * expect($injector.get('$injector')).toBe($injector);
3832 * expect($injector.invoke(function($injector) {
3833 * return $injector;
3834 * })).toBe($injector);
3835 * ```
3836 *
3837 * # Injection Function Annotation
3838 *
3839 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3840 * following are all valid ways of annotating function with injection arguments and are equivalent.
3841 *
3842 * ```js
3843 * // inferred (only works if code not minified/obfuscated)
3844 * $injector.invoke(function(serviceA){});
3845 *
3846 * // annotated
3847 * function explicit(serviceA) {};
3848 * explicit.$inject = ['serviceA'];
3849 * $injector.invoke(explicit);
3850 *
3851 * // inline
3852 * $injector.invoke(['serviceA', function(serviceA){}]);
3853 * ```
3854 *
3855 * ## Inference
3856 *
3857 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3858 * can then be parsed and the function arguments can be extracted. This method of discovering
3859 * annotations is disallowed when the injector is in strict mode.
3860 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3861 * argument names.
3862 *
3863 * ## `$inject` Annotation
3864 * By adding an `$inject` property onto a function the injection parameters can be specified.
3865 *
3866 * ## Inline
3867 * As an array of injection names, where the last item in the array is the function to call.
3868 */
3869
3870/**
3871 * @ngdoc method
3872 * @name $injector#get
3873 *
3874 * @description
3875 * Return an instance of the service.
3876 *
3877 * @param {string} name The name of the instance to retrieve.
3878 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3879 * @return {*} The instance.
3880 */
3881
3882/**
3883 * @ngdoc method
3884 * @name $injector#invoke
3885 *
3886 * @description
3887 * Invoke the method and supply the method arguments from the `$injector`.
3888 *
3889 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3890 * injected according to the {@link guide/di $inject Annotation} rules.
3891 * @param {Object=} self The `this` for the invoked method.
3892 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3893 * object first, before the `$injector` is consulted.
3894 * @returns {*} the value returned by the invoked `fn` function.
3895 */
3896
3897/**
3898 * @ngdoc method
3899 * @name $injector#has
3900 *
3901 * @description
3902 * Allows the user to query if the particular service exists.
3903 *
3904 * @param {string} name Name of the service to query.
3905 * @returns {boolean} `true` if injector has given service.
3906 */
3907
3908/**
3909 * @ngdoc method
3910 * @name $injector#instantiate
3911 * @description
3912 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3913 * operator, and supplies all of the arguments to the constructor function as specified by the
3914 * constructor annotation.
3915 *
3916 * @param {Function} Type Annotated constructor function.
3917 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3918 * object first, before the `$injector` is consulted.
3919 * @returns {Object} new instance of `Type`.
3920 */
3921
3922/**
3923 * @ngdoc method
3924 * @name $injector#annotate
3925 *
3926 * @description
3927 * Returns an array of service names which the function is requesting for injection. This API is
3928 * used by the injector to determine which services need to be injected into the function when the
3929 * function is invoked. There are three ways in which the function can be annotated with the needed
3930 * dependencies.
3931 *
3932 * # Argument names
3933 *
3934 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3935 * by converting the function into a string using `toString()` method and extracting the argument
3936 * names.
3937 * ```js
3938 * // Given
3939 * function MyController($scope, $route) {
3940 * // ...
3941 * }
3942 *
3943 * // Then
3944 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
3945 * ```
3946 *
3947 * You can disallow this method by using strict injection mode.
3948 *
3949 * This method does not work with code minification / obfuscation. For this reason the following
3950 * annotation strategies are supported.
3951 *
3952 * # The `$inject` property
3953 *
3954 * If a function has an `$inject` property and its value is an array of strings, then the strings
3955 * represent names of services to be injected into the function.
3956 * ```js
3957 * // Given
3958 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
3959 * // ...
3960 * }
3961 * // Define function dependencies
3962 * MyController['$inject'] = ['$scope', '$route'];
3963 *
3964 * // Then
3965 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
3966 * ```
3967 *
3968 * # The array notation
3969 *
3970 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
3971 * is very inconvenient. In these situations using the array notation to specify the dependencies in
3972 * a way that survives minification is a better choice:
3973 *
3974 * ```js
3975 * // We wish to write this (not minification / obfuscation safe)
3976 * injector.invoke(function($compile, $rootScope) {
3977 * // ...
3978 * });
3979 *
3980 * // We are forced to write break inlining
3981 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
3982 * // ...
3983 * };
3984 * tmpFn.$inject = ['$compile', '$rootScope'];
3985 * injector.invoke(tmpFn);
3986 *
3987 * // To better support inline function the inline annotation is supported
3988 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
3989 * // ...
3990 * }]);
3991 *
3992 * // Therefore
3993 * expect(injector.annotate(
3994 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
3995 * ).toEqual(['$compile', '$rootScope']);
3996 * ```
3997 *
3998 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
3999 * be retrieved as described above.
4000 *
4001 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4002 *
4003 * @returns {Array.<string>} The names of the services which the function requires.
4004 */
4005
4006
4007
4008
4009/**
4010 * @ngdoc service
4011 * @name $provide
4012 *
4013 * @description
4014 *
4015 * The {@link auto.$provide $provide} service has a number of methods for registering components
4016 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4017 * {@link angular.Module}.
4018 *
4019 * An Angular **service** is a singleton object created by a **service factory**. These **service
4020 * factories** are functions which, in turn, are created by a **service provider**.
4021 * The **service providers** are constructor functions. When instantiated they must contain a
4022 * property called `$get`, which holds the **service factory** function.
4023 *
4024 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4025 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4026 * function to get the instance of the **service**.
4027 *
4028 * Often services have no configuration options and there is no need to add methods to the service
4029 * provider. The provider will be no more than a constructor function with a `$get` property. For
4030 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4031 * services without specifying a provider.
4032 *
4033 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4034 * {@link auto.$injector $injector}
4035 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4036 * providers and services.
4037 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4038 * services, not providers.
4039 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4040 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4041 * given factory function.
4042 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4043 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4044 * a new object using the given constructor function.
4045 *
4046 * See the individual methods for more information and examples.
4047 */
4048
4049/**
4050 * @ngdoc method
4051 * @name $provide#provider
4052 * @description
4053 *
4054 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4055 * are constructor functions, whose instances are responsible for "providing" a factory for a
4056 * service.
4057 *
4058 * Service provider names start with the name of the service they provide followed by `Provider`.
4059 * For example, the {@link ng.$log $log} service has a provider called
4060 * {@link ng.$logProvider $logProvider}.
4061 *
4062 * Service provider objects can have additional methods which allow configuration of the provider
4063 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4064 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4065 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4066 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4067 * console or not.
4068 *
4069 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4070 'Provider'` key.
4071 * @param {(Object|function())} provider If the provider is:
4072 *
4073 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4074 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4075 * - `Constructor`: a new instance of the provider will be created using
4076 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4077 *
4078 * @returns {Object} registered provider instance
4079
4080 * @example
4081 *
4082 * The following example shows how to create a simple event tracking service and register it using
4083 * {@link auto.$provide#provider $provide.provider()}.
4084 *
4085 * ```js
4086 * // Define the eventTracker provider
4087 * function EventTrackerProvider() {
4088 * var trackingUrl = '/track';
4089 *
4090 * // A provider method for configuring where the tracked events should been saved
4091 * this.setTrackingUrl = function(url) {
4092 * trackingUrl = url;
4093 * };
4094 *
4095 * // The service factory function
4096 * this.$get = ['$http', function($http) {
4097 * var trackedEvents = {};
4098 * return {
4099 * // Call this to track an event
4100 * event: function(event) {
4101 * var count = trackedEvents[event] || 0;
4102 * count += 1;
4103 * trackedEvents[event] = count;
4104 * return count;
4105 * },
4106 * // Call this to save the tracked events to the trackingUrl
4107 * save: function() {
4108 * $http.post(trackingUrl, trackedEvents);
4109 * }
4110 * };
4111 * }];
4112 * }
4113 *
4114 * describe('eventTracker', function() {
4115 * var postSpy;
4116 *
4117 * beforeEach(module(function($provide) {
4118 * // Register the eventTracker provider
4119 * $provide.provider('eventTracker', EventTrackerProvider);
4120 * }));
4121 *
4122 * beforeEach(module(function(eventTrackerProvider) {
4123 * // Configure eventTracker provider
4124 * eventTrackerProvider.setTrackingUrl('/custom-track');
4125 * }));
4126 *
4127 * it('tracks events', inject(function(eventTracker) {
4128 * expect(eventTracker.event('login')).toEqual(1);
4129 * expect(eventTracker.event('login')).toEqual(2);
4130 * }));
4131 *
4132 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4133 * postSpy = spyOn($http, 'post');
4134 * eventTracker.event('login');
4135 * eventTracker.save();
4136 * expect(postSpy).toHaveBeenCalled();
4137 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4138 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4139 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4140 * }));
4141 * });
4142 * ```
4143 */
4144
4145/**
4146 * @ngdoc method
4147 * @name $provide#factory
4148 * @description
4149 *
4150 * Register a **service factory**, which will be called to return the service instance.
4151 * This is short for registering a service where its provider consists of only a `$get` property,
4152 * which is the given service factory function.
4153 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4154 * configure your service in a provider.
4155 *
4156 * @param {string} name The name of the instance.
4157 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4158 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4159 * @returns {Object} registered provider instance
4160 *
4161 * @example
4162 * Here is an example of registering a service
4163 * ```js
4164 * $provide.factory('ping', ['$http', function($http) {
4165 * return function ping() {
4166 * return $http.send('/ping');
4167 * };
4168 * }]);
4169 * ```
4170 * You would then inject and use this service like this:
4171 * ```js
4172 * someModule.controller('Ctrl', ['ping', function(ping) {
4173 * ping();
4174 * }]);
4175 * ```
4176 */
4177
4178
4179/**
4180 * @ngdoc method
4181 * @name $provide#service
4182 * @description
4183 *
4184 * Register a **service constructor**, which will be invoked with `new` to create the service
4185 * instance.
4186 * This is short for registering a service where its provider's `$get` property is the service
4187 * constructor function that will be used to instantiate the service instance.
4188 *
4189 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4190 * as a type/class.
4191 *
4192 * @param {string} name The name of the instance.
4193 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4194 * that will be instantiated.
4195 * @returns {Object} registered provider instance
4196 *
4197 * @example
4198 * Here is an example of registering a service using
4199 * {@link auto.$provide#service $provide.service(class)}.
4200 * ```js
4201 * var Ping = function($http) {
4202 * this.$http = $http;
4203 * };
4204 *
4205 * Ping.$inject = ['$http'];
4206 *
4207 * Ping.prototype.send = function() {
4208 * return this.$http.get('/ping');
4209 * };
4210 * $provide.service('ping', Ping);
4211 * ```
4212 * You would then inject and use this service like this:
4213 * ```js
4214 * someModule.controller('Ctrl', ['ping', function(ping) {
4215 * ping.send();
4216 * }]);
4217 * ```
4218 */
4219
4220
4221/**
4222 * @ngdoc method
4223 * @name $provide#value
4224 * @description
4225 *
4226 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4227 * number, an array, an object or a function. This is short for registering a service where its
4228 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4229 * service**.
4230 *
4231 * Value services are similar to constant services, except that they cannot be injected into a
4232 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4233 * an Angular
4234 * {@link auto.$provide#decorator decorator}.
4235 *
4236 * @param {string} name The name of the instance.
4237 * @param {*} value The value.
4238 * @returns {Object} registered provider instance
4239 *
4240 * @example
4241 * Here are some examples of creating value services.
4242 * ```js
4243 * $provide.value('ADMIN_USER', 'admin');
4244 *
4245 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4246 *
4247 * $provide.value('halfOf', function(value) {
4248 * return value / 2;
4249 * });
4250 * ```
4251 */
4252
4253
4254/**
4255 * @ngdoc method
4256 * @name $provide#constant
4257 * @description
4258 *
4259 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4260 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4261 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4262 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4263 *
4264 * @param {string} name The name of the constant.
4265 * @param {*} value The constant value.
4266 * @returns {Object} registered instance
4267 *
4268 * @example
4269 * Here a some examples of creating constants:
4270 * ```js
4271 * $provide.constant('SHARD_HEIGHT', 306);
4272 *
4273 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4274 *
4275 * $provide.constant('double', function(value) {
4276 * return value * 2;
4277 * });
4278 * ```
4279 */
4280
4281
4282/**
4283 * @ngdoc method
4284 * @name $provide#decorator
4285 * @description
4286 *
4287 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4288 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4289 * service. The object returned by the decorator may be the original service, or a new service
4290 * object which replaces or wraps and delegates to the original service.
4291 *
4292 * @param {string} name The name of the service to decorate.
4293 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4294 * instantiated and should return the decorated service instance. The function is called using
4295 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4296 * Local injection arguments:
4297 *
4298 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4299 * decorated or delegated to.
4300 *
4301 * @example
4302 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4303 * calls to {@link ng.$log#error $log.warn()}.
4304 * ```js
4305 * $provide.decorator('$log', ['$delegate', function($delegate) {
4306 * $delegate.warn = $delegate.error;
4307 * return $delegate;
4308 * }]);
4309 * ```
4310 */
4311
4312
4313function createInjector(modulesToLoad, strictDi) {
4314 strictDi = (strictDi === true);
4315 var INSTANTIATING = {},
4316 providerSuffix = 'Provider',
4317 path = [],
4318 loadedModules = new HashMap([], true),
4319 providerCache = {
4320 $provide: {
4321 provider: supportObject(provider),
4322 factory: supportObject(factory),
4323 service: supportObject(service),
4324 value: supportObject(value),
4325 constant: supportObject(constant),
4326 decorator: decorator
4327 }
4328 },
4329 providerInjector = (providerCache.$injector =
4330 createInternalInjector(providerCache, function(serviceName, caller) {
4331 if (angular.isString(caller)) {
4332 path.push(caller);
4333 }
4334 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4335 })),
4336 instanceCache = {},
4337 instanceInjector = (instanceCache.$injector =
4338 createInternalInjector(instanceCache, function(serviceName, caller) {
4339 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4340 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4341 }));
4342
4343
4344 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4345
4346 return instanceInjector;
4347
4348 ////////////////////////////////////
4349 // $provider
4350 ////////////////////////////////////
4351
4352 function supportObject(delegate) {
4353 return function(key, value) {
4354 if (isObject(key)) {
4355 forEach(key, reverseParams(delegate));
4356 } else {
4357 return delegate(key, value);
4358 }
4359 };
4360 }
4361
4362 function provider(name, provider_) {
4363 assertNotHasOwnProperty(name, 'service');
4364 if (isFunction(provider_) || isArray(provider_)) {
4365 provider_ = providerInjector.instantiate(provider_);
4366 }
4367 if (!provider_.$get) {
4368 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4369 }
4370 return providerCache[name + providerSuffix] = provider_;
4371 }
4372
4373 function enforceReturnValue(name, factory) {
4374 return function enforcedReturnValue() {
4375 var result = instanceInjector.invoke(factory, this);
4376 if (isUndefined(result)) {
4377 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4378 }
4379 return result;
4380 };
4381 }
4382
4383 function factory(name, factoryFn, enforce) {
4384 return provider(name, {
4385 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4386 });
4387 }
4388
4389 function service(name, constructor) {
4390 return factory(name, ['$injector', function($injector) {
4391 return $injector.instantiate(constructor);
4392 }]);
4393 }
4394
4395 function value(name, val) { return factory(name, valueFn(val), false); }
4396
4397 function constant(name, value) {
4398 assertNotHasOwnProperty(name, 'constant');
4399 providerCache[name] = value;
4400 instanceCache[name] = value;
4401 }
4402
4403 function decorator(serviceName, decorFn) {
4404 var origProvider = providerInjector.get(serviceName + providerSuffix),
4405 orig$get = origProvider.$get;
4406
4407 origProvider.$get = function() {
4408 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4409 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4410 };
4411 }
4412
4413 ////////////////////////////////////
4414 // Module Loading
4415 ////////////////////////////////////
4416 function loadModules(modulesToLoad) {
4417 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4418 var runBlocks = [], moduleFn;
4419 forEach(modulesToLoad, function(module) {
4420 if (loadedModules.get(module)) return;
4421 loadedModules.put(module, true);
4422
4423 function runInvokeQueue(queue) {
4424 var i, ii;
4425 for (i = 0, ii = queue.length; i < ii; i++) {
4426 var invokeArgs = queue[i],
4427 provider = providerInjector.get(invokeArgs[0]);
4428
4429 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4430 }
4431 }
4432
4433 try {
4434 if (isString(module)) {
4435 moduleFn = angularModule(module);
4436 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4437 runInvokeQueue(moduleFn._invokeQueue);
4438 runInvokeQueue(moduleFn._configBlocks);
4439 } else if (isFunction(module)) {
4440 runBlocks.push(providerInjector.invoke(module));
4441 } else if (isArray(module)) {
4442 runBlocks.push(providerInjector.invoke(module));
4443 } else {
4444 assertArgFn(module, 'module');
4445 }
4446 } catch (e) {
4447 if (isArray(module)) {
4448 module = module[module.length - 1];
4449 }
4450 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4451 // Safari & FF's stack traces don't contain error.message content
4452 // unlike those of Chrome and IE
4453 // So if stack doesn't contain message, we create a new string that contains both.
4454 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4455 /* jshint -W022 */
4456 e = e.message + '\n' + e.stack;
4457 }
4458 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4459 module, e.stack || e.message || e);
4460 }
4461 });
4462 return runBlocks;
4463 }
4464
4465 ////////////////////////////////////
4466 // internal Injector
4467 ////////////////////////////////////
4468
4469 function createInternalInjector(cache, factory) {
4470
4471 function getService(serviceName, caller) {
4472 if (cache.hasOwnProperty(serviceName)) {
4473 if (cache[serviceName] === INSTANTIATING) {
4474 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4475 serviceName + ' <- ' + path.join(' <- '));
4476 }
4477 return cache[serviceName];
4478 } else {
4479 try {
4480 path.unshift(serviceName);
4481 cache[serviceName] = INSTANTIATING;
4482 return cache[serviceName] = factory(serviceName, caller);
4483 } catch (err) {
4484 if (cache[serviceName] === INSTANTIATING) {
4485 delete cache[serviceName];
4486 }
4487 throw err;
4488 } finally {
4489 path.shift();
4490 }
4491 }
4492 }
4493
4494 function invoke(fn, self, locals, serviceName) {
4495 if (typeof locals === 'string') {
4496 serviceName = locals;
4497 locals = null;
4498 }
4499
4500 var args = [],
4501 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4502 length, i,
4503 key;
4504
4505 for (i = 0, length = $inject.length; i < length; i++) {
4506 key = $inject[i];
4507 if (typeof key !== 'string') {
4508 throw $injectorMinErr('itkn',
4509 'Incorrect injection token! Expected service name as string, got {0}', key);
4510 }
4511 args.push(
4512 locals && locals.hasOwnProperty(key)
4513 ? locals[key]
4514 : getService(key, serviceName)
4515 );
4516 }
4517 if (isArray(fn)) {
4518 fn = fn[length];
4519 }
4520
4521 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4522 // #5388
4523 return fn.apply(self, args);
4524 }
4525
4526 function instantiate(Type, locals, serviceName) {
4527 // Check if Type is annotated and use just the given function at n-1 as parameter
4528 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4529 // Object creation: http://jsperf.com/create-constructor/2
4530 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4531 var returnedValue = invoke(Type, instance, locals, serviceName);
4532
4533 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4534 }
4535
4536 return {
4537 invoke: invoke,
4538 instantiate: instantiate,
4539 get: getService,
4540 annotate: createInjector.$$annotate,
4541 has: function(name) {
4542 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4543 }
4544 };
4545 }
4546}
4547
4548createInjector.$$annotate = annotate;
4549
4550/**
4551 * @ngdoc provider
4552 * @name $anchorScrollProvider
4553 *
4554 * @description
4555 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4556 * {@link ng.$location#hash $location.hash()} changes.
4557 */
4558function $AnchorScrollProvider() {
4559
4560 var autoScrollingEnabled = true;
4561
4562 /**
4563 * @ngdoc method
4564 * @name $anchorScrollProvider#disableAutoScrolling
4565 *
4566 * @description
4567 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4568 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4569 * Use this method to disable automatic scrolling.
4570 *
4571 * If automatic scrolling is disabled, one must explicitly call
4572 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4573 * current hash.
4574 */
4575 this.disableAutoScrolling = function() {
4576 autoScrollingEnabled = false;
4577 };
4578
4579 /**
4580 * @ngdoc service
4581 * @name $anchorScroll
4582 * @kind function
4583 * @requires $window
4584 * @requires $location
4585 * @requires $rootScope
4586 *
4587 * @description
4588 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4589 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4590 * in the
4591 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4592 *
4593 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4594 * match any anchor whenever it changes. This can be disabled by calling
4595 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4596 *
4597 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4598 * vertical scroll-offset (either fixed or dynamic).
4599 *
4600 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4601 * {@link ng.$location#hash $location.hash()} will be used.
4602 *
4603 * @property {(number|function|jqLite)} yOffset
4604 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4605 * positioned elements at the top of the page, such as navbars, headers etc.
4606 *
4607 * `yOffset` can be specified in various ways:
4608 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4609 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4610 * a number representing the offset (in pixels).<br /><br />
4611 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4612 * the top of the page to the element's bottom will be used as offset.<br />
4613 * **Note**: The element will be taken into account only as long as its `position` is set to
4614 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4615 * their height and/or positioning according to the viewport's size.
4616 *
4617 * <br />
4618 * <div class="alert alert-warning">
4619 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4620 * not some child element.
4621 * </div>
4622 *
4623 * @example
4624 <example module="anchorScrollExample">
4625 <file name="index.html">
4626 <div id="scrollArea" ng-controller="ScrollController">
4627 <a ng-click="gotoBottom()">Go to bottom</a>
4628 <a id="bottom"></a> You're at the bottom!
4629 </div>
4630 </file>
4631 <file name="script.js">
4632 angular.module('anchorScrollExample', [])
4633 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4634 function ($scope, $location, $anchorScroll) {
4635 $scope.gotoBottom = function() {
4636 // set the location.hash to the id of
4637 // the element you wish to scroll to.
4638 $location.hash('bottom');
4639
4640 // call $anchorScroll()
4641 $anchorScroll();
4642 };
4643 }]);
4644 </file>
4645 <file name="style.css">
4646 #scrollArea {
4647 height: 280px;
4648 overflow: auto;
4649 }
4650
4651 #bottom {
4652 display: block;
4653 margin-top: 2000px;
4654 }
4655 </file>
4656 </example>
4657 *
4658 * <hr />
4659 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4660 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4661 *
4662 * @example
4663 <example module="anchorScrollOffsetExample">
4664 <file name="index.html">
4665 <div class="fixed-header" ng-controller="headerCtrl">
4666 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4667 Go to anchor {{x}}
4668 </a>
4669 </div>
4670 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4671 Anchor {{x}} of 5
4672 </div>
4673 </file>
4674 <file name="script.js">
4675 angular.module('anchorScrollOffsetExample', [])
4676 .run(['$anchorScroll', function($anchorScroll) {
4677 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4678 }])
4679 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4680 function ($anchorScroll, $location, $scope) {
4681 $scope.gotoAnchor = function(x) {
4682 var newHash = 'anchor' + x;
4683 if ($location.hash() !== newHash) {
4684 // set the $location.hash to `newHash` and
4685 // $anchorScroll will automatically scroll to it
4686 $location.hash('anchor' + x);
4687 } else {
4688 // call $anchorScroll() explicitly,
4689 // since $location.hash hasn't changed
4690 $anchorScroll();
4691 }
4692 };
4693 }
4694 ]);
4695 </file>
4696 <file name="style.css">
4697 body {
4698 padding-top: 50px;
4699 }
4700
4701 .anchor {
4702 border: 2px dashed DarkOrchid;
4703 padding: 10px 10px 200px 10px;
4704 }
4705
4706 .fixed-header {
4707 background-color: rgba(0, 0, 0, 0.2);
4708 height: 50px;
4709 position: fixed;
4710 top: 0; left: 0; right: 0;
4711 }
4712
4713 .fixed-header > a {
4714 display: inline-block;
4715 margin: 5px 15px;
4716 }
4717 </file>
4718 </example>
4719 */
4720 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4721 var document = $window.document;
4722
4723 // Helper function to get first anchor from a NodeList
4724 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4725 // and working in all supported browsers.)
4726 function getFirstAnchor(list) {
4727 var result = null;
4728 Array.prototype.some.call(list, function(element) {
4729 if (nodeName_(element) === 'a') {
4730 result = element;
4731 return true;
4732 }
4733 });
4734 return result;
4735 }
4736
4737 function getYOffset() {
4738
4739 var offset = scroll.yOffset;
4740
4741 if (isFunction(offset)) {
4742 offset = offset();
4743 } else if (isElement(offset)) {
4744 var elem = offset[0];
4745 var style = $window.getComputedStyle(elem);
4746 if (style.position !== 'fixed') {
4747 offset = 0;
4748 } else {
4749 offset = elem.getBoundingClientRect().bottom;
4750 }
4751 } else if (!isNumber(offset)) {
4752 offset = 0;
4753 }
4754
4755 return offset;
4756 }
4757
4758 function scrollTo(elem) {
4759 if (elem) {
4760 elem.scrollIntoView();
4761
4762 var offset = getYOffset();
4763
4764 if (offset) {
4765 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4766 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4767 // top of the viewport.
4768 //
4769 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4770 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4771 // way down the page.
4772 //
4773 // This is often the case for elements near the bottom of the page.
4774 //
4775 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4776 // the top of the element and the offset, which is enough to align the top of `elem` at the
4777 // desired position.
4778 var elemTop = elem.getBoundingClientRect().top;
4779 $window.scrollBy(0, elemTop - offset);
4780 }
4781 } else {
4782 $window.scrollTo(0, 0);
4783 }
4784 }
4785
4786 function scroll(hash) {
4787 hash = isString(hash) ? hash : $location.hash();
4788 var elm;
4789
4790 // empty hash, scroll to the top of the page
4791 if (!hash) scrollTo(null);
4792
4793 // element with given id
4794 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4795
4796 // first anchor with given name :-D
4797 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4798
4799 // no element and hash == 'top', scroll to the top of the page
4800 else if (hash === 'top') scrollTo(null);
4801 }
4802
4803 // does not scroll when user clicks on anchor link that is currently on
4804 // (no url change, no $location.hash() change), browser native does scroll
4805 if (autoScrollingEnabled) {
4806 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4807 function autoScrollWatchAction(newVal, oldVal) {
4808 // skip the initial scroll if $location.hash is empty
4809 if (newVal === oldVal && newVal === '') return;
4810
4811 jqLiteDocumentLoaded(function() {
4812 $rootScope.$evalAsync(scroll);
4813 });
4814 });
4815 }
4816
4817 return scroll;
4818 }];
4819}
4820
4821var $animateMinErr = minErr('$animate');
4822var ELEMENT_NODE = 1;
4823var NG_ANIMATE_CLASSNAME = 'ng-animate';
4824
4825function mergeClasses(a,b) {
4826 if (!a && !b) return '';
4827 if (!a) return b;
4828 if (!b) return a;
4829 if (isArray(a)) a = a.join(' ');
4830 if (isArray(b)) b = b.join(' ');
4831 return a + ' ' + b;
4832}
4833
4834function extractElementNode(element) {
4835 for (var i = 0; i < element.length; i++) {
4836 var elm = element[i];
4837 if (elm.nodeType === ELEMENT_NODE) {
4838 return elm;
4839 }
4840 }
4841}
4842
4843function splitClasses(classes) {
4844 if (isString(classes)) {
4845 classes = classes.split(' ');
4846 }
4847
4848 // Use createMap() to prevent class assumptions involving property names in
4849 // Object.prototype
4850 var obj = createMap();
4851 forEach(classes, function(klass) {
4852 // sometimes the split leaves empty string values
4853 // incase extra spaces were applied to the options
4854 if (klass.length) {
4855 obj[klass] = true;
4856 }
4857 });
4858 return obj;
4859}
4860
4861// if any other type of options value besides an Object value is
4862// passed into the $animate.method() animation then this helper code
4863// will be run which will ignore it. While this patch is not the
4864// greatest solution to this, a lot of existing plugins depend on
4865// $animate to either call the callback (< 1.2) or return a promise
4866// that can be changed. This helper function ensures that the options
4867// are wiped clean incase a callback function is provided.
4868function prepareAnimateOptions(options) {
4869 return isObject(options)
4870 ? options
4871 : {};
4872}
4873
4874var $$CoreAnimateRunnerProvider = function() {
4875 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4876 function AnimateRunner() {}
4877 AnimateRunner.all = noop;
4878 AnimateRunner.chain = noop;
4879 AnimateRunner.prototype = {
4880 end: noop,
4881 cancel: noop,
4882 resume: noop,
4883 pause: noop,
4884 complete: noop,
4885 then: function(pass, fail) {
4886 return $q(function(resolve) {
4887 $$rAF(function() {
4888 resolve();
4889 });
4890 }).then(pass, fail);
4891 }
4892 };
4893 return AnimateRunner;
4894 }];
4895};
4896
4897// this is prefixed with Core since it conflicts with
4898// the animateQueueProvider defined in ngAnimate/animateQueue.js
4899var $$CoreAnimateQueueProvider = function() {
4900 var postDigestQueue = new HashMap();
4901 var postDigestElements = [];
4902
4903 this.$get = ['$$AnimateRunner', '$rootScope',
4904 function($$AnimateRunner, $rootScope) {
4905 return {
4906 enabled: noop,
4907 on: noop,
4908 off: noop,
4909 pin: noop,
4910
4911 push: function(element, event, options, domOperation) {
4912 domOperation && domOperation();
4913
4914 options = options || {};
4915 options.from && element.css(options.from);
4916 options.to && element.css(options.to);
4917
4918 if (options.addClass || options.removeClass) {
4919 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
4920 }
4921
4922 return new $$AnimateRunner(); // jshint ignore:line
4923 }
4924 };
4925
4926
4927 function updateData(data, classes, value) {
4928 var changed = false;
4929 if (classes) {
4930 classes = isString(classes) ? classes.split(' ') :
4931 isArray(classes) ? classes : [];
4932 forEach(classes, function(className) {
4933 if (className) {
4934 changed = true;
4935 data[className] = value;
4936 }
4937 });
4938 }
4939 return changed;
4940 }
4941
4942 function handleCSSClassChanges() {
4943 forEach(postDigestElements, function(element) {
4944 var data = postDigestQueue.get(element);
4945 if (data) {
4946 var existing = splitClasses(element.attr('class'));
4947 var toAdd = '';
4948 var toRemove = '';
4949 forEach(data, function(status, className) {
4950 var hasClass = !!existing[className];
4951 if (status !== hasClass) {
4952 if (status) {
4953 toAdd += (toAdd.length ? ' ' : '') + className;
4954 } else {
4955 toRemove += (toRemove.length ? ' ' : '') + className;
4956 }
4957 }
4958 });
4959
4960 forEach(element, function(elm) {
4961 toAdd && jqLiteAddClass(elm, toAdd);
4962 toRemove && jqLiteRemoveClass(elm, toRemove);
4963 });
4964 postDigestQueue.remove(element);
4965 }
4966 });
4967 postDigestElements.length = 0;
4968 }
4969
4970
4971 function addRemoveClassesPostDigest(element, add, remove) {
4972 var data = postDigestQueue.get(element) || {};
4973
4974 var classesAdded = updateData(data, add, true);
4975 var classesRemoved = updateData(data, remove, false);
4976
4977 if (classesAdded || classesRemoved) {
4978
4979 postDigestQueue.put(element, data);
4980 postDigestElements.push(element);
4981
4982 if (postDigestElements.length === 1) {
4983 $rootScope.$$postDigest(handleCSSClassChanges);
4984 }
4985 }
4986 }
4987 }];
4988};
4989
4990/**
4991 * @ngdoc provider
4992 * @name $animateProvider
4993 *
4994 * @description
4995 * Default implementation of $animate that doesn't perform any animations, instead just
4996 * synchronously performs DOM updates and resolves the returned runner promise.
4997 *
4998 * In order to enable animations the `ngAnimate` module has to be loaded.
4999 *
5000 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5001 */
5002var $AnimateProvider = ['$provide', function($provide) {
5003 var provider = this;
5004
5005 this.$$registeredAnimations = Object.create(null);
5006
5007 /**
5008 * @ngdoc method
5009 * @name $animateProvider#register
5010 *
5011 * @description
5012 * Registers a new injectable animation factory function. The factory function produces the
5013 * animation object which contains callback functions for each event that is expected to be
5014 * animated.
5015 *
5016 * * `eventFn`: `function(element, ... , doneFunction, options)`
5017 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5018 * on the type of animation additional arguments will be injected into the animation function. The
5019 * list below explains the function signatures for the different animation methods:
5020 *
5021 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5022 * - addClass: function(element, addedClasses, doneFunction, options)
5023 * - removeClass: function(element, removedClasses, doneFunction, options)
5024 * - enter, leave, move: function(element, doneFunction, options)
5025 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5026 *
5027 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5028 *
5029 * ```js
5030 * return {
5031 * //enter, leave, move signature
5032 * eventFn : function(element, done, options) {
5033 * //code to run the animation
5034 * //once complete, then run done()
5035 * return function endFunction(wasCancelled) {
5036 * //code to cancel the animation
5037 * }
5038 * }
5039 * }
5040 * ```
5041 *
5042 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5043 * @param {Function} factory The factory function that will be executed to return the animation
5044 * object.
5045 */
5046 this.register = function(name, factory) {
5047 if (name && name.charAt(0) !== '.') {
5048 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5049 }
5050
5051 var key = name + '-animation';
5052 provider.$$registeredAnimations[name.substr(1)] = key;
5053 $provide.factory(key, factory);
5054 };
5055
5056 /**
5057 * @ngdoc method
5058 * @name $animateProvider#classNameFilter
5059 *
5060 * @description
5061 * Sets and/or returns the CSS class regular expression that is checked when performing
5062 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5063 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5064 * When setting the `classNameFilter` value, animations will only be performed on elements
5065 * that successfully match the filter expression. This in turn can boost performance
5066 * for low-powered devices as well as applications containing a lot of structural operations.
5067 * @param {RegExp=} expression The className expression which will be checked against all animations
5068 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5069 */
5070 this.classNameFilter = function(expression) {
5071 if (arguments.length === 1) {
5072 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5073 if (this.$$classNameFilter) {
5074 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5075 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5076 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5077
5078 }
5079 }
5080 }
5081 return this.$$classNameFilter;
5082 };
5083
5084 this.$get = ['$$animateQueue', function($$animateQueue) {
5085 function domInsert(element, parentElement, afterElement) {
5086 // if for some reason the previous element was removed
5087 // from the dom sometime before this code runs then let's
5088 // just stick to using the parent element as the anchor
5089 if (afterElement) {
5090 var afterNode = extractElementNode(afterElement);
5091 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5092 afterElement = null;
5093 }
5094 }
5095 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5096 }
5097
5098 /**
5099 * @ngdoc service
5100 * @name $animate
5101 * @description The $animate service exposes a series of DOM utility methods that provide support
5102 * for animation hooks. The default behavior is the application of DOM operations, however,
5103 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5104 * to ensure that animation runs with the triggered DOM operation.
5105 *
5106 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5107 * included and only when it is active then the animation hooks that `$animate` triggers will be
5108 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5109 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5110 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5111 *
5112 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5113 *
5114 * To learn more about enabling animation support, click here to visit the
5115 * {@link ngAnimate ngAnimate module page}.
5116 */
5117 return {
5118 // we don't call it directly since non-existant arguments may
5119 // be interpreted as null within the sub enabled function
5120
5121 /**
5122 *
5123 * @ngdoc method
5124 * @name $animate#on
5125 * @kind function
5126 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5127 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5128 * is fired with the following params:
5129 *
5130 * ```js
5131 * $animate.on('enter', container,
5132 * function callback(element, phase) {
5133 * // cool we detected an enter animation within the container
5134 * }
5135 * );
5136 * ```
5137 *
5138 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5139 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5140 * as well as among its children
5141 * @param {Function} callback the callback function that will be fired when the listener is triggered
5142 *
5143 * The arguments present in the callback function are:
5144 * * `element` - The captured DOM element that the animation was fired on.
5145 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5146 */
5147 on: $$animateQueue.on,
5148
5149 /**
5150 *
5151 * @ngdoc method
5152 * @name $animate#off
5153 * @kind function
5154 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5155 * can be used in three different ways depending on the arguments:
5156 *
5157 * ```js
5158 * // remove all the animation event listeners listening for `enter`
5159 * $animate.off('enter');
5160 *
5161 * // remove all the animation event listeners listening for `enter` on the given element and its children
5162 * $animate.off('enter', container);
5163 *
5164 * // remove the event listener function provided by `listenerFn` that is set
5165 * // to listen for `enter` on the given `element` as well as its children
5166 * $animate.off('enter', container, callback);
5167 * ```
5168 *
5169 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5170 * @param {DOMElement=} container the container element the event listener was placed on
5171 * @param {Function=} callback the callback function that was registered as the listener
5172 */
5173 off: $$animateQueue.off,
5174
5175 /**
5176 * @ngdoc method
5177 * @name $animate#pin
5178 * @kind function
5179 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5180 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5181 * element despite being outside the realm of the application or within another application. Say for example if the application
5182 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5183 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5184 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5185 *
5186 * Note that this feature is only active when the `ngAnimate` module is used.
5187 *
5188 * @param {DOMElement} element the external element that will be pinned
5189 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5190 */
5191 pin: $$animateQueue.pin,
5192
5193 /**
5194 *
5195 * @ngdoc method
5196 * @name $animate#enabled
5197 * @kind function
5198 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5199 * function can be called in four ways:
5200 *
5201 * ```js
5202 * // returns true or false
5203 * $animate.enabled();
5204 *
5205 * // changes the enabled state for all animations
5206 * $animate.enabled(false);
5207 * $animate.enabled(true);
5208 *
5209 * // returns true or false if animations are enabled for an element
5210 * $animate.enabled(element);
5211 *
5212 * // changes the enabled state for an element and its children
5213 * $animate.enabled(element, true);
5214 * $animate.enabled(element, false);
5215 * ```
5216 *
5217 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5218 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5219 *
5220 * @return {boolean} whether or not animations are enabled
5221 */
5222 enabled: $$animateQueue.enabled,
5223
5224 /**
5225 * @ngdoc method
5226 * @name $animate#cancel
5227 * @kind function
5228 * @description Cancels the provided animation.
5229 *
5230 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5231 */
5232 cancel: function(runner) {
5233 runner.end && runner.end();
5234 },
5235
5236 /**
5237 *
5238 * @ngdoc method
5239 * @name $animate#enter
5240 * @kind function
5241 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5242 * as the first child within the `parent` element and then triggers an animation.
5243 * A promise is returned that will be resolved during the next digest once the animation
5244 * has completed.
5245 *
5246 * @param {DOMElement} element the element which will be inserted into the DOM
5247 * @param {DOMElement} parent the parent element which will append the element as
5248 * a child (so long as the after element is not present)
5249 * @param {DOMElement=} after the sibling element after which the element will be appended
5250 * @param {object=} options an optional collection of options/styles that will be applied to the element
5251 *
5252 * @return {Promise} the animation callback promise
5253 */
5254 enter: function(element, parent, after, options) {
5255 parent = parent && jqLite(parent);
5256 after = after && jqLite(after);
5257 parent = parent || after.parent();
5258 domInsert(element, parent, after);
5259 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5260 },
5261
5262 /**
5263 *
5264 * @ngdoc method
5265 * @name $animate#move
5266 * @kind function
5267 * @description Inserts (moves) the element into its new position in the DOM either after
5268 * the `after` element (if provided) or as the first child within the `parent` element
5269 * and then triggers an animation. A promise is returned that will be resolved
5270 * during the next digest once the animation has completed.
5271 *
5272 * @param {DOMElement} element the element which will be moved into the new DOM position
5273 * @param {DOMElement} parent the parent element which will append the element as
5274 * a child (so long as the after element is not present)
5275 * @param {DOMElement=} after the sibling element after which the element will be appended
5276 * @param {object=} options an optional collection of options/styles that will be applied to the element
5277 *
5278 * @return {Promise} the animation callback promise
5279 */
5280 move: function(element, parent, after, options) {
5281 parent = parent && jqLite(parent);
5282 after = after && jqLite(after);
5283 parent = parent || after.parent();
5284 domInsert(element, parent, after);
5285 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5286 },
5287
5288 /**
5289 * @ngdoc method
5290 * @name $animate#leave
5291 * @kind function
5292 * @description Triggers an animation and then removes the element from the DOM.
5293 * When the function is called a promise is returned that will be resolved during the next
5294 * digest once the animation has completed.
5295 *
5296 * @param {DOMElement} element the element which will be removed from the DOM
5297 * @param {object=} options an optional collection of options/styles that will be applied to the element
5298 *
5299 * @return {Promise} the animation callback promise
5300 */
5301 leave: function(element, options) {
5302 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5303 element.remove();
5304 });
5305 },
5306
5307 /**
5308 * @ngdoc method
5309 * @name $animate#addClass
5310 * @kind function
5311 *
5312 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5313 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5314 * animation if element already contains the CSS class or if the class is removed at a later step.
5315 * Note that class-based animations are treated differently compared to structural animations
5316 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5317 * depending if CSS or JavaScript animations are used.
5318 *
5319 * @param {DOMElement} element the element which the CSS classes will be applied to
5320 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5321 * @param {object=} options an optional collection of options/styles that will be applied to the element
5322 *
5323 * @return {Promise} the animation callback promise
5324 */
5325 addClass: function(element, className, options) {
5326 options = prepareAnimateOptions(options);
5327 options.addClass = mergeClasses(options.addclass, className);
5328 return $$animateQueue.push(element, 'addClass', options);
5329 },
5330
5331 /**
5332 * @ngdoc method
5333 * @name $animate#removeClass
5334 * @kind function
5335 *
5336 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5337 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5338 * animation if element does not contain the CSS class or if the class is added at a later step.
5339 * Note that class-based animations are treated differently compared to structural animations
5340 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5341 * depending if CSS or JavaScript animations are used.
5342 *
5343 * @param {DOMElement} element the element which the CSS classes will be applied to
5344 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5345 * @param {object=} options an optional collection of options/styles that will be applied to the element
5346 *
5347 * @return {Promise} the animation callback promise
5348 */
5349 removeClass: function(element, className, options) {
5350 options = prepareAnimateOptions(options);
5351 options.removeClass = mergeClasses(options.removeClass, className);
5352 return $$animateQueue.push(element, 'removeClass', options);
5353 },
5354
5355 /**
5356 * @ngdoc method
5357 * @name $animate#setClass
5358 * @kind function
5359 *
5360 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5361 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5362 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5363 * passed. Note that class-based animations are treated differently compared to structural animations
5364 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5365 * depending if CSS or JavaScript animations are used.
5366 *
5367 * @param {DOMElement} element the element which the CSS classes will be applied to
5368 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5369 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5370 * @param {object=} options an optional collection of options/styles that will be applied to the element
5371 *
5372 * @return {Promise} the animation callback promise
5373 */
5374 setClass: function(element, add, remove, options) {
5375 options = prepareAnimateOptions(options);
5376 options.addClass = mergeClasses(options.addClass, add);
5377 options.removeClass = mergeClasses(options.removeClass, remove);
5378 return $$animateQueue.push(element, 'setClass', options);
5379 },
5380
5381 /**
5382 * @ngdoc method
5383 * @name $animate#animate
5384 * @kind function
5385 *
5386 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5387 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5388 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5389 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5390 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5391 *
5392 * @param {DOMElement} element the element which the CSS styles will be applied to
5393 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5394 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5395 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5396 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5397 * (Note that if no animation is detected then this value will not be appplied to the element.)
5398 * @param {object=} options an optional collection of options/styles that will be applied to the element
5399 *
5400 * @return {Promise} the animation callback promise
5401 */
5402 animate: function(element, from, to, className, options) {
5403 options = prepareAnimateOptions(options);
5404 options.from = options.from ? extend(options.from, from) : from;
5405 options.to = options.to ? extend(options.to, to) : to;
5406
5407 className = className || 'ng-inline-animate';
5408 options.tempClasses = mergeClasses(options.tempClasses, className);
5409 return $$animateQueue.push(element, 'animate', options);
5410 }
5411 };
5412 }];
5413}];
5414
5415/**
5416 * @ngdoc service
5417 * @name $animateCss
5418 * @kind object
5419 *
5420 * @description
5421 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5422 * then the `$animateCss` service will actually perform animations.
5423 *
5424 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5425 */
5426var $CoreAnimateCssProvider = function() {
5427 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5428
5429 var RAFPromise = function() {};
5430 RAFPromise.prototype = {
5431 done: function(cancel) {
5432 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5433 },
5434 end: function() {
5435 this.done();
5436 },
5437 cancel: function() {
5438 this.done(true);
5439 },
5440 getPromise: function() {
5441 if (!this.defer) {
5442 this.defer = $q.defer();
5443 }
5444 return this.defer.promise;
5445 },
5446 then: function(f1,f2) {
5447 return this.getPromise().then(f1,f2);
5448 },
5449 'catch': function(f1) {
5450 return this.getPromise()['catch'](f1);
5451 },
5452 'finally': function(f1) {
5453 return this.getPromise()['finally'](f1);
5454 }
5455 };
5456
5457 return function(element, options) {
5458 // there is no point in applying the styles since
5459 // there is no animation that goes on at all in
5460 // this version of $animateCss.
5461 if (options.cleanupStyles) {
5462 options.from = options.to = null;
5463 }
5464
5465 if (options.from) {
5466 element.css(options.from);
5467 options.from = null;
5468 }
5469
5470 var closed, runner = new RAFPromise();
5471 return {
5472 start: run,
5473 end: run
5474 };
5475
5476 function run() {
5477 $$rAF(function() {
5478 close();
5479 if (!closed) {
5480 runner.done();
5481 }
5482 closed = true;
5483 });
5484 return runner;
5485 }
5486
5487 function close() {
5488 if (options.addClass) {
5489 element.addClass(options.addClass);
5490 options.addClass = null;
5491 }
5492 if (options.removeClass) {
5493 element.removeClass(options.removeClass);
5494 options.removeClass = null;
5495 }
5496 if (options.to) {
5497 element.css(options.to);
5498 options.to = null;
5499 }
5500 }
5501 };
5502 }];
5503};
5504
5505/* global stripHash: true */
5506
5507/**
5508 * ! This is a private undocumented service !
5509 *
5510 * @name $browser
5511 * @requires $log
5512 * @description
5513 * This object has two goals:
5514 *
5515 * - hide all the global state in the browser caused by the window object
5516 * - abstract away all the browser specific features and inconsistencies
5517 *
5518 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5519 * service, which can be used for convenient testing of the application without the interaction with
5520 * the real browser apis.
5521 */
5522/**
5523 * @param {object} window The global window object.
5524 * @param {object} document jQuery wrapped document.
5525 * @param {object} $log window.console or an object with the same interface.
5526 * @param {object} $sniffer $sniffer service
5527 */
5528function Browser(window, document, $log, $sniffer) {
5529 var self = this,
5530 rawDocument = document[0],
5531 location = window.location,
5532 history = window.history,
5533 setTimeout = window.setTimeout,
5534 clearTimeout = window.clearTimeout,
5535 pendingDeferIds = {};
5536
5537 self.isMock = false;
5538
5539 var outstandingRequestCount = 0;
5540 var outstandingRequestCallbacks = [];
5541
5542 // TODO(vojta): remove this temporary api
5543 self.$$completeOutstandingRequest = completeOutstandingRequest;
5544 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5545
5546 /**
5547 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5548 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5549 */
5550 function completeOutstandingRequest(fn) {
5551 try {
5552 fn.apply(null, sliceArgs(arguments, 1));
5553 } finally {
5554 outstandingRequestCount--;
5555 if (outstandingRequestCount === 0) {
5556 while (outstandingRequestCallbacks.length) {
5557 try {
5558 outstandingRequestCallbacks.pop()();
5559 } catch (e) {
5560 $log.error(e);
5561 }
5562 }
5563 }
5564 }
5565 }
5566
5567 function getHash(url) {
5568 var index = url.indexOf('#');
5569 return index === -1 ? '' : url.substr(index);
5570 }
5571
5572 /**
5573 * @private
5574 * Note: this method is used only by scenario runner
5575 * TODO(vojta): prefix this method with $$ ?
5576 * @param {function()} callback Function that will be called when no outstanding request
5577 */
5578 self.notifyWhenNoOutstandingRequests = function(callback) {
5579 if (outstandingRequestCount === 0) {
5580 callback();
5581 } else {
5582 outstandingRequestCallbacks.push(callback);
5583 }
5584 };
5585
5586 //////////////////////////////////////////////////////////////
5587 // URL API
5588 //////////////////////////////////////////////////////////////
5589
5590 var cachedState, lastHistoryState,
5591 lastBrowserUrl = location.href,
5592 baseElement = document.find('base'),
5593 pendingLocation = null;
5594
5595 cacheState();
5596 lastHistoryState = cachedState;
5597
5598 /**
5599 * @name $browser#url
5600 *
5601 * @description
5602 * GETTER:
5603 * Without any argument, this method just returns current value of location.href.
5604 *
5605 * SETTER:
5606 * With at least one argument, this method sets url to new value.
5607 * If html5 history api supported, pushState/replaceState is used, otherwise
5608 * location.href/location.replace is used.
5609 * Returns its own instance to allow chaining
5610 *
5611 * NOTE: this api is intended for use only by the $location service. Please use the
5612 * {@link ng.$location $location service} to change url.
5613 *
5614 * @param {string} url New url (when used as setter)
5615 * @param {boolean=} replace Should new url replace current history record?
5616 * @param {object=} state object to use with pushState/replaceState
5617 */
5618 self.url = function(url, replace, state) {
5619 // In modern browsers `history.state` is `null` by default; treating it separately
5620 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5621 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5622 if (isUndefined(state)) {
5623 state = null;
5624 }
5625
5626 // Android Browser BFCache causes location, history reference to become stale.
5627 if (location !== window.location) location = window.location;
5628 if (history !== window.history) history = window.history;
5629
5630 // setter
5631 if (url) {
5632 var sameState = lastHistoryState === state;
5633
5634 // Don't change anything if previous and current URLs and states match. This also prevents
5635 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5636 // See https://github.com/angular/angular.js/commit/ffb2701
5637 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5638 return self;
5639 }
5640 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5641 lastBrowserUrl = url;
5642 lastHistoryState = state;
5643 // Don't use history API if only the hash changed
5644 // due to a bug in IE10/IE11 which leads
5645 // to not firing a `hashchange` nor `popstate` event
5646 // in some cases (see #9143).
5647 if ($sniffer.history && (!sameBase || !sameState)) {
5648 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5649 cacheState();
5650 // Do the assignment again so that those two variables are referentially identical.
5651 lastHistoryState = cachedState;
5652 } else {
5653 if (!sameBase || pendingLocation) {
5654 pendingLocation = url;
5655 }
5656 if (replace) {
5657 location.replace(url);
5658 } else if (!sameBase) {
5659 location.href = url;
5660 } else {
5661 location.hash = getHash(url);
5662 }
5663 if (location.href !== url) {
5664 pendingLocation = url;
5665 }
5666 }
5667 return self;
5668 // getter
5669 } else {
5670 // - pendingLocation is needed as browsers don't allow to read out
5671 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5672 // https://openradar.appspot.com/22186109).
5673 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5674 return pendingLocation || location.href.replace(/%27/g,"'");
5675 }
5676 };
5677
5678 /**
5679 * @name $browser#state
5680 *
5681 * @description
5682 * This method is a getter.
5683 *
5684 * Return history.state or null if history.state is undefined.
5685 *
5686 * @returns {object} state
5687 */
5688 self.state = function() {
5689 return cachedState;
5690 };
5691
5692 var urlChangeListeners = [],
5693 urlChangeInit = false;
5694
5695 function cacheStateAndFireUrlChange() {
5696 pendingLocation = null;
5697 cacheState();
5698 fireUrlChange();
5699 }
5700
5701 function getCurrentState() {
5702 try {
5703 return history.state;
5704 } catch (e) {
5705 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5706 }
5707 }
5708
5709 // This variable should be used *only* inside the cacheState function.
5710 var lastCachedState = null;
5711 function cacheState() {
5712 // This should be the only place in $browser where `history.state` is read.
5713 cachedState = getCurrentState();
5714 cachedState = isUndefined(cachedState) ? null : cachedState;
5715
5716 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5717 if (equals(cachedState, lastCachedState)) {
5718 cachedState = lastCachedState;
5719 }
5720 lastCachedState = cachedState;
5721 }
5722
5723 function fireUrlChange() {
5724 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5725 return;
5726 }
5727
5728 lastBrowserUrl = self.url();
5729 lastHistoryState = cachedState;
5730 forEach(urlChangeListeners, function(listener) {
5731 listener(self.url(), cachedState);
5732 });
5733 }
5734
5735 /**
5736 * @name $browser#onUrlChange
5737 *
5738 * @description
5739 * Register callback function that will be called, when url changes.
5740 *
5741 * It's only called when the url is changed from outside of angular:
5742 * - user types different url into address bar
5743 * - user clicks on history (forward/back) button
5744 * - user clicks on a link
5745 *
5746 * It's not called when url is changed by $browser.url() method
5747 *
5748 * The listener gets called with new url as parameter.
5749 *
5750 * NOTE: this api is intended for use only by the $location service. Please use the
5751 * {@link ng.$location $location service} to monitor url changes in angular apps.
5752 *
5753 * @param {function(string)} listener Listener function to be called when url changes.
5754 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5755 */
5756 self.onUrlChange = function(callback) {
5757 // TODO(vojta): refactor to use node's syntax for events
5758 if (!urlChangeInit) {
5759 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5760 // don't fire popstate when user change the address bar and don't fire hashchange when url
5761 // changed by push/replaceState
5762
5763 // html5 history api - popstate event
5764 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5765 // hashchange event
5766 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5767
5768 urlChangeInit = true;
5769 }
5770
5771 urlChangeListeners.push(callback);
5772 return callback;
5773 };
5774
5775 /**
5776 * @private
5777 * Remove popstate and hashchange handler from window.
5778 *
5779 * NOTE: this api is intended for use only by $rootScope.
5780 */
5781 self.$$applicationDestroyed = function() {
5782 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5783 };
5784
5785 /**
5786 * Checks whether the url has changed outside of Angular.
5787 * Needs to be exported to be able to check for changes that have been done in sync,
5788 * as hashchange/popstate events fire in async.
5789 */
5790 self.$$checkUrlChange = fireUrlChange;
5791
5792 //////////////////////////////////////////////////////////////
5793 // Misc API
5794 //////////////////////////////////////////////////////////////
5795
5796 /**
5797 * @name $browser#baseHref
5798 *
5799 * @description
5800 * Returns current <base href>
5801 * (always relative - without domain)
5802 *
5803 * @returns {string} The current base href
5804 */
5805 self.baseHref = function() {
5806 var href = baseElement.attr('href');
5807 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5808 };
5809
5810 /**
5811 * @name $browser#defer
5812 * @param {function()} fn A function, who's execution should be deferred.
5813 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5814 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5815 *
5816 * @description
5817 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5818 *
5819 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5820 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5821 * via `$browser.defer.flush()`.
5822 *
5823 */
5824 self.defer = function(fn, delay) {
5825 var timeoutId;
5826 outstandingRequestCount++;
5827 timeoutId = setTimeout(function() {
5828 delete pendingDeferIds[timeoutId];
5829 completeOutstandingRequest(fn);
5830 }, delay || 0);
5831 pendingDeferIds[timeoutId] = true;
5832 return timeoutId;
5833 };
5834
5835
5836 /**
5837 * @name $browser#defer.cancel
5838 *
5839 * @description
5840 * Cancels a deferred task identified with `deferId`.
5841 *
5842 * @param {*} deferId Token returned by the `$browser.defer` function.
5843 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5844 * canceled.
5845 */
5846 self.defer.cancel = function(deferId) {
5847 if (pendingDeferIds[deferId]) {
5848 delete pendingDeferIds[deferId];
5849 clearTimeout(deferId);
5850 completeOutstandingRequest(noop);
5851 return true;
5852 }
5853 return false;
5854 };
5855
5856}
5857
5858function $BrowserProvider() {
5859 this.$get = ['$window', '$log', '$sniffer', '$document',
5860 function($window, $log, $sniffer, $document) {
5861 return new Browser($window, $document, $log, $sniffer);
5862 }];
5863}
5864
5865/**
5866 * @ngdoc service
5867 * @name $cacheFactory
5868 *
5869 * @description
5870 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5871 * them.
5872 *
5873 * ```js
5874 *
5875 * var cache = $cacheFactory('cacheId');
5876 * expect($cacheFactory.get('cacheId')).toBe(cache);
5877 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5878 *
5879 * cache.put("key", "value");
5880 * cache.put("another key", "another value");
5881 *
5882 * // We've specified no options on creation
5883 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5884 *
5885 * ```
5886 *
5887 *
5888 * @param {string} cacheId Name or id of the newly created cache.
5889 * @param {object=} options Options object that specifies the cache behavior. Properties:
5890 *
5891 * - `{number=}` `capacity` — turns the cache into LRU cache.
5892 *
5893 * @returns {object} Newly created cache object with the following set of methods:
5894 *
5895 * - `{object}` `info()` — Returns id, size, and options of cache.
5896 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5897 * it.
5898 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5899 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5900 * - `{void}` `removeAll()` — Removes all cached values.
5901 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5902 *
5903 * @example
5904 <example module="cacheExampleApp">
5905 <file name="index.html">
5906 <div ng-controller="CacheController">
5907 <input ng-model="newCacheKey" placeholder="Key">
5908 <input ng-model="newCacheValue" placeholder="Value">
5909 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5910
5911 <p ng-if="keys.length">Cached Values</p>
5912 <div ng-repeat="key in keys">
5913 <span ng-bind="key"></span>
5914 <span>: </span>
5915 <b ng-bind="cache.get(key)"></b>
5916 </div>
5917
5918 <p>Cache Info</p>
5919 <div ng-repeat="(key, value) in cache.info()">
5920 <span ng-bind="key"></span>
5921 <span>: </span>
5922 <b ng-bind="value"></b>
5923 </div>
5924 </div>
5925 </file>
5926 <file name="script.js">
5927 angular.module('cacheExampleApp', []).
5928 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
5929 $scope.keys = [];
5930 $scope.cache = $cacheFactory('cacheId');
5931 $scope.put = function(key, value) {
5932 if (angular.isUndefined($scope.cache.get(key))) {
5933 $scope.keys.push(key);
5934 }
5935 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
5936 };
5937 }]);
5938 </file>
5939 <file name="style.css">
5940 p {
5941 margin: 10px 0 3px;
5942 }
5943 </file>
5944 </example>
5945 */
5946function $CacheFactoryProvider() {
5947
5948 this.$get = function() {
5949 var caches = {};
5950
5951 function cacheFactory(cacheId, options) {
5952 if (cacheId in caches) {
5953 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
5954 }
5955
5956 var size = 0,
5957 stats = extend({}, options, {id: cacheId}),
5958 data = createMap(),
5959 capacity = (options && options.capacity) || Number.MAX_VALUE,
5960 lruHash = createMap(),
5961 freshEnd = null,
5962 staleEnd = null;
5963
5964 /**
5965 * @ngdoc type
5966 * @name $cacheFactory.Cache
5967 *
5968 * @description
5969 * A cache object used to store and retrieve data, primarily used by
5970 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
5971 * templates and other data.
5972 *
5973 * ```js
5974 * angular.module('superCache')
5975 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
5976 * return $cacheFactory('super-cache');
5977 * }]);
5978 * ```
5979 *
5980 * Example test:
5981 *
5982 * ```js
5983 * it('should behave like a cache', inject(function(superCache) {
5984 * superCache.put('key', 'value');
5985 * superCache.put('another key', 'another value');
5986 *
5987 * expect(superCache.info()).toEqual({
5988 * id: 'super-cache',
5989 * size: 2
5990 * });
5991 *
5992 * superCache.remove('another key');
5993 * expect(superCache.get('another key')).toBeUndefined();
5994 *
5995 * superCache.removeAll();
5996 * expect(superCache.info()).toEqual({
5997 * id: 'super-cache',
5998 * size: 0
5999 * });
6000 * }));
6001 * ```
6002 */
6003 return caches[cacheId] = {
6004
6005 /**
6006 * @ngdoc method
6007 * @name $cacheFactory.Cache#put
6008 * @kind function
6009 *
6010 * @description
6011 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6012 * retrieved later, and incrementing the size of the cache if the key was not already
6013 * present in the cache. If behaving like an LRU cache, it will also remove stale
6014 * entries from the set.
6015 *
6016 * It will not insert undefined values into the cache.
6017 *
6018 * @param {string} key the key under which the cached data is stored.
6019 * @param {*} value the value to store alongside the key. If it is undefined, the key
6020 * will not be stored.
6021 * @returns {*} the value stored.
6022 */
6023 put: function(key, value) {
6024 if (isUndefined(value)) return;
6025 if (capacity < Number.MAX_VALUE) {
6026 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6027
6028 refresh(lruEntry);
6029 }
6030
6031 if (!(key in data)) size++;
6032 data[key] = value;
6033
6034 if (size > capacity) {
6035 this.remove(staleEnd.key);
6036 }
6037
6038 return value;
6039 },
6040
6041 /**
6042 * @ngdoc method
6043 * @name $cacheFactory.Cache#get
6044 * @kind function
6045 *
6046 * @description
6047 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6048 *
6049 * @param {string} key the key of the data to be retrieved
6050 * @returns {*} the value stored.
6051 */
6052 get: function(key) {
6053 if (capacity < Number.MAX_VALUE) {
6054 var lruEntry = lruHash[key];
6055
6056 if (!lruEntry) return;
6057
6058 refresh(lruEntry);
6059 }
6060
6061 return data[key];
6062 },
6063
6064
6065 /**
6066 * @ngdoc method
6067 * @name $cacheFactory.Cache#remove
6068 * @kind function
6069 *
6070 * @description
6071 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6072 *
6073 * @param {string} key the key of the entry to be removed
6074 */
6075 remove: function(key) {
6076 if (capacity < Number.MAX_VALUE) {
6077 var lruEntry = lruHash[key];
6078
6079 if (!lruEntry) return;
6080
6081 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6082 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6083 link(lruEntry.n,lruEntry.p);
6084
6085 delete lruHash[key];
6086 }
6087
6088 if (!(key in data)) return;
6089
6090 delete data[key];
6091 size--;
6092 },
6093
6094
6095 /**
6096 * @ngdoc method
6097 * @name $cacheFactory.Cache#removeAll
6098 * @kind function
6099 *
6100 * @description
6101 * Clears the cache object of any entries.
6102 */
6103 removeAll: function() {
6104 data = createMap();
6105 size = 0;
6106 lruHash = createMap();
6107 freshEnd = staleEnd = null;
6108 },
6109
6110
6111 /**
6112 * @ngdoc method
6113 * @name $cacheFactory.Cache#destroy
6114 * @kind function
6115 *
6116 * @description
6117 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6118 * removing it from the {@link $cacheFactory $cacheFactory} set.
6119 */
6120 destroy: function() {
6121 data = null;
6122 stats = null;
6123 lruHash = null;
6124 delete caches[cacheId];
6125 },
6126
6127
6128 /**
6129 * @ngdoc method
6130 * @name $cacheFactory.Cache#info
6131 * @kind function
6132 *
6133 * @description
6134 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6135 *
6136 * @returns {object} an object with the following properties:
6137 * <ul>
6138 * <li>**id**: the id of the cache instance</li>
6139 * <li>**size**: the number of entries kept in the cache instance</li>
6140 * <li>**...**: any additional properties from the options object when creating the
6141 * cache.</li>
6142 * </ul>
6143 */
6144 info: function() {
6145 return extend({}, stats, {size: size});
6146 }
6147 };
6148
6149
6150 /**
6151 * makes the `entry` the freshEnd of the LRU linked list
6152 */
6153 function refresh(entry) {
6154 if (entry != freshEnd) {
6155 if (!staleEnd) {
6156 staleEnd = entry;
6157 } else if (staleEnd == entry) {
6158 staleEnd = entry.n;
6159 }
6160
6161 link(entry.n, entry.p);
6162 link(entry, freshEnd);
6163 freshEnd = entry;
6164 freshEnd.n = null;
6165 }
6166 }
6167
6168
6169 /**
6170 * bidirectionally links two entries of the LRU linked list
6171 */
6172 function link(nextEntry, prevEntry) {
6173 if (nextEntry != prevEntry) {
6174 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6175 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6176 }
6177 }
6178 }
6179
6180
6181 /**
6182 * @ngdoc method
6183 * @name $cacheFactory#info
6184 *
6185 * @description
6186 * Get information about all the caches that have been created
6187 *
6188 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6189 */
6190 cacheFactory.info = function() {
6191 var info = {};
6192 forEach(caches, function(cache, cacheId) {
6193 info[cacheId] = cache.info();
6194 });
6195 return info;
6196 };
6197
6198
6199 /**
6200 * @ngdoc method
6201 * @name $cacheFactory#get
6202 *
6203 * @description
6204 * Get access to a cache object by the `cacheId` used when it was created.
6205 *
6206 * @param {string} cacheId Name or id of a cache to access.
6207 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6208 */
6209 cacheFactory.get = function(cacheId) {
6210 return caches[cacheId];
6211 };
6212
6213
6214 return cacheFactory;
6215 };
6216}
6217
6218/**
6219 * @ngdoc service
6220 * @name $templateCache
6221 *
6222 * @description
6223 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6224 * can load templates directly into the cache in a `script` tag, or by consuming the
6225 * `$templateCache` service directly.
6226 *
6227 * Adding via the `script` tag:
6228 *
6229 * ```html
6230 * <script type="text/ng-template" id="templateId.html">
6231 * <p>This is the content of the template</p>
6232 * </script>
6233 * ```
6234 *
6235 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6236 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6237 * element with ng-app attribute), otherwise the template will be ignored.
6238 *
6239 * Adding via the `$templateCache` service:
6240 *
6241 * ```js
6242 * var myApp = angular.module('myApp', []);
6243 * myApp.run(function($templateCache) {
6244 * $templateCache.put('templateId.html', 'This is the content of the template');
6245 * });
6246 * ```
6247 *
6248 * To retrieve the template later, simply use it in your HTML:
6249 * ```html
6250 * <div ng-include=" 'templateId.html' "></div>
6251 * ```
6252 *
6253 * or get it via Javascript:
6254 * ```js
6255 * $templateCache.get('templateId.html')
6256 * ```
6257 *
6258 * See {@link ng.$cacheFactory $cacheFactory}.
6259 *
6260 */
6261function $TemplateCacheProvider() {
6262 this.$get = ['$cacheFactory', function($cacheFactory) {
6263 return $cacheFactory('templates');
6264 }];
6265}
6266
6267/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6268 * Any commits to this file should be reviewed with security in mind. *
6269 * Changes to this file can potentially create security vulnerabilities. *
6270 * An approval from 2 Core members with history of modifying *
6271 * this file is required. *
6272 * *
6273 * Does the change somehow allow for arbitrary javascript to be executed? *
6274 * Or allows for someone to change the prototype of built-in objects? *
6275 * Or gives undesired access to variables likes document or window? *
6276 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6277
6278/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6279 *
6280 * DOM-related variables:
6281 *
6282 * - "node" - DOM Node
6283 * - "element" - DOM Element or Node
6284 * - "$node" or "$element" - jqLite-wrapped node or element
6285 *
6286 *
6287 * Compiler related stuff:
6288 *
6289 * - "linkFn" - linking fn of a single directive
6290 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6291 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6292 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6293 */
6294
6295
6296/**
6297 * @ngdoc service
6298 * @name $compile
6299 * @kind function
6300 *
6301 * @description
6302 * Compiles an HTML string or DOM into a template and produces a template function, which
6303 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6304 *
6305 * The compilation is a process of walking the DOM tree and matching DOM elements to
6306 * {@link ng.$compileProvider#directive directives}.
6307 *
6308 * <div class="alert alert-warning">
6309 * **Note:** This document is an in-depth reference of all directive options.
6310 * For a gentle introduction to directives with examples of common use cases,
6311 * see the {@link guide/directive directive guide}.
6312 * </div>
6313 *
6314 * ## Comprehensive Directive API
6315 *
6316 * There are many different options for a directive.
6317 *
6318 * The difference resides in the return value of the factory function.
6319 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6320 * or just the `postLink` function (all other properties will have the default values).
6321 *
6322 * <div class="alert alert-success">
6323 * **Best Practice:** It's recommended to use the "directive definition object" form.
6324 * </div>
6325 *
6326 * Here's an example directive declared with a Directive Definition Object:
6327 *
6328 * ```js
6329 * var myModule = angular.module(...);
6330 *
6331 * myModule.directive('directiveName', function factory(injectables) {
6332 * var directiveDefinitionObject = {
6333 * priority: 0,
6334 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6335 * // or
6336 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6337 * transclude: false,
6338 * restrict: 'A',
6339 * templateNamespace: 'html',
6340 * scope: false,
6341 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6342 * controllerAs: 'stringIdentifier',
6343 * bindToController: false,
6344 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6345 * compile: function compile(tElement, tAttrs, transclude) {
6346 * return {
6347 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6348 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6349 * }
6350 * // or
6351 * // return function postLink( ... ) { ... }
6352 * },
6353 * // or
6354 * // link: {
6355 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6356 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6357 * // }
6358 * // or
6359 * // link: function postLink( ... ) { ... }
6360 * };
6361 * return directiveDefinitionObject;
6362 * });
6363 * ```
6364 *
6365 * <div class="alert alert-warning">
6366 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6367 * </div>
6368 *
6369 * Therefore the above can be simplified as:
6370 *
6371 * ```js
6372 * var myModule = angular.module(...);
6373 *
6374 * myModule.directive('directiveName', function factory(injectables) {
6375 * var directiveDefinitionObject = {
6376 * link: function postLink(scope, iElement, iAttrs) { ... }
6377 * };
6378 * return directiveDefinitionObject;
6379 * // or
6380 * // return function postLink(scope, iElement, iAttrs) { ... }
6381 * });
6382 * ```
6383 *
6384 *
6385 *
6386 * ### Directive Definition Object
6387 *
6388 * The directive definition object provides instructions to the {@link ng.$compile
6389 * compiler}. The attributes are:
6390 *
6391 * #### `multiElement`
6392 * When this property is set to true, the HTML compiler will collect DOM nodes between
6393 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6394 * together as the directive elements. It is recommended that this feature be used on directives
6395 * which are not strictly behavioural (such as {@link ngClick}), and which
6396 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6397 *
6398 * #### `priority`
6399 * When there are multiple directives defined on a single DOM element, sometimes it
6400 * is necessary to specify the order in which the directives are applied. The `priority` is used
6401 * to sort the directives before their `compile` functions get called. Priority is defined as a
6402 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6403 * are also run in priority order, but post-link functions are run in reverse order. The order
6404 * of directives with the same priority is undefined. The default priority is `0`.
6405 *
6406 * #### `terminal`
6407 * If set to true then the current `priority` will be the last set of directives
6408 * which will execute (any directives at the current priority will still execute
6409 * as the order of execution on same `priority` is undefined). Note that expressions
6410 * and other directives used in the directive's template will also be excluded from execution.
6411 *
6412 * #### `scope`
6413 * The scope property can be `true`, an object or a falsy value:
6414 *
6415 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6416 *
6417 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6418 * the directive's element. If multiple directives on the same element request a new scope,
6419 * only one new scope is created. The new scope rule does not apply for the root of the template
6420 * since the root of the template always gets a new scope.
6421 *
6422 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6423 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6424 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6425 * data in the parent scope.
6426 *
6427 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6428 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6429 * the object hash map to the name of the property on the isolate scope; the values define how the property
6430 * is bound to the parent scope, via matching attributes on the directive's element:
6431 *
6432 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6433 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6434 * attribute name is assumed to be the same as the local name.
6435 * Given `<widget my-attr="hello {{name}}">` and widget definition
6436 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6437 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6438 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6439 * component scope).
6440 *
6441 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6442 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6443 * name is specified then the attribute name is assumed to be the same as the local name.
6444 * Given `<widget my-attr="parentModel">` and widget definition of
6445 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6446 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6447 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6448 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6449 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6450 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6451 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6452 *
6453 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6454 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6455 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6456 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6457 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6458 * pass data from the isolated scope via an expression to the parent scope, this can be
6459 * done by passing a map of local variable names and values into the expression wrapper fn.
6460 * For example, if the expression is `increment(amount)` then we can specify the amount value
6461 * by calling the `localFn` as `localFn({amount: 22})`.
6462 *
6463 * In general it's possible to apply more than one directive to one element, but there might be limitations
6464 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6465 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6466 *
6467 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6468 * * **child scope** + **no scope** => Both directives will share one single child scope
6469 * * **child scope** + **child scope** => Both directives will share one single child scope
6470 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6471 * its parent's scope
6472 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6473 * be applied to the same element.
6474 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6475 * cannot be applied to the same element.
6476 *
6477 *
6478 * #### `bindToController`
6479 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6480 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6481 * is instantiated, the initial values of the isolate scope bindings are already available.
6482 *
6483 * #### `controller`
6484 * Controller constructor function. The controller is instantiated before the
6485 * pre-linking phase and can be accessed by other directives (see
6486 * `require` attribute). This allows the directives to communicate with each other and augment
6487 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6488 *
6489 * * `$scope` - Current scope associated with the element
6490 * * `$element` - Current element
6491 * * `$attrs` - Current attributes object for the element
6492 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6493 * `function([scope], cloneLinkingFn, futureParentElement)`.
6494 * * `scope`: optional argument to override the scope.
6495 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6496 * * `futureParentElement`:
6497 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6498 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6499 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6500 * and when the `cloneLinkinFn` is passed,
6501 * as those elements need to created and cloned in a special way when they are defined outside their
6502 * usual containers (e.g. like `<svg>`).
6503 * * See also the `directive.templateNamespace` property.
6504 *
6505 *
6506 * #### `require`
6507 * Require another directive and inject its controller as the fourth argument to the linking function. The
6508 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6509 * injected argument will be an array in corresponding order. If no such directive can be
6510 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6511 * is specified, in which case error checking is skipped). The name can be prefixed with:
6512 *
6513 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6514 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6515 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6516 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6517 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6518 * `null` to the `link` fn if not found.
6519 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6520 * `null` to the `link` fn if not found.
6521 *
6522 *
6523 * #### `controllerAs`
6524 * Identifier name for a reference to the controller in the directive's scope.
6525 * This allows the controller to be referenced from the directive template. This is especially
6526 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6527 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6528 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6529 *
6530 *
6531 * #### `restrict`
6532 * String of subset of `EACM` which restricts the directive to a specific directive
6533 * declaration style. If omitted, the defaults (elements and attributes) are used.
6534 *
6535 * * `E` - Element name (default): `<my-directive></my-directive>`
6536 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6537 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6538 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6539 *
6540 *
6541 * #### `templateNamespace`
6542 * String representing the document type used by the markup in the template.
6543 * AngularJS needs this information as those elements need to be created and cloned
6544 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6545 *
6546 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6547 * top-level elements such as `<svg>` or `<math>`.
6548 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6549 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6550 *
6551 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6552 *
6553 * #### `template`
6554 * HTML markup that may:
6555 * * Replace the contents of the directive's element (default).
6556 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6557 * * Wrap the contents of the directive's element (if `transclude` is true).
6558 *
6559 * Value may be:
6560 *
6561 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6562 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6563 * function api below) and returns a string value.
6564 *
6565 *
6566 * #### `templateUrl`
6567 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6568 *
6569 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6570 * for later when the template has been resolved. In the meantime it will continue to compile and link
6571 * sibling and parent elements as though this element had not contained any directives.
6572 *
6573 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6574 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6575 * case when only one deeply nested directive has `templateUrl`.
6576 *
6577 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6578 *
6579 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6580 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6581 * a string value representing the url. In either case, the template URL is passed through {@link
6582 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6583 *
6584 *
6585 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6586 * specify what the template should replace. Defaults to `false`.
6587 *
6588 * * `true` - the template will replace the directive's element.
6589 * * `false` - the template will replace the contents of the directive's element.
6590 *
6591 * The replacement process migrates all of the attributes / classes from the old element to the new
6592 * one. See the {@link guide/directive#template-expanding-directive
6593 * Directives Guide} for an example.
6594 *
6595 * There are very few scenarios where element replacement is required for the application function,
6596 * the main one being reusable custom components that are used within SVG contexts
6597 * (because SVG doesn't work with custom elements in the DOM tree).
6598 *
6599 * #### `transclude`
6600 * Extract the contents of the element where the directive appears and make it available to the directive.
6601 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6602 * {@link $compile#transclusion Transclusion} section below.
6603 *
6604 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6605 * directive's element or the entire element:
6606 *
6607 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6608 * * `'element'` - transclude the whole of the directive's element including any directives on this
6609 * element that defined at a lower priority than this directive. When used, the `template`
6610 * property is ignored.
6611 *
6612 *
6613 * #### `compile`
6614 *
6615 * ```js
6616 * function compile(tElement, tAttrs, transclude) { ... }
6617 * ```
6618 *
6619 * The compile function deals with transforming the template DOM. Since most directives do not do
6620 * template transformation, it is not used often. The compile function takes the following arguments:
6621 *
6622 * * `tElement` - template element - The element where the directive has been declared. It is
6623 * safe to do template transformation on the element and child elements only.
6624 *
6625 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6626 * between all directive compile functions.
6627 *
6628 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6629 *
6630 * <div class="alert alert-warning">
6631 * **Note:** The template instance and the link instance may be different objects if the template has
6632 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6633 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6634 * should be done in a linking function rather than in a compile function.
6635 * </div>
6636
6637 * <div class="alert alert-warning">
6638 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6639 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6640 * stack overflow errors.
6641 *
6642 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6643 * a directive's template instead of relying on automatic template compilation via `template` or
6644 * `templateUrl` declaration or manual compilation inside the compile function.
6645 * </div>
6646 *
6647 * <div class="alert alert-danger">
6648 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6649 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6650 * to the link function instead.
6651 * </div>
6652
6653 * A compile function can have a return value which can be either a function or an object.
6654 *
6655 * * returning a (post-link) function - is equivalent to registering the linking function via the
6656 * `link` property of the config object when the compile function is empty.
6657 *
6658 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6659 * control when a linking function should be called during the linking phase. See info about
6660 * pre-linking and post-linking functions below.
6661 *
6662 *
6663 * #### `link`
6664 * This property is used only if the `compile` property is not defined.
6665 *
6666 * ```js
6667 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6668 * ```
6669 *
6670 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6671 * executed after the template has been cloned. This is where most of the directive logic will be
6672 * put.
6673 *
6674 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6675 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6676 *
6677 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6678 * manipulate the children of the element only in `postLink` function since the children have
6679 * already been linked.
6680 *
6681 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6682 * between all directive linking functions.
6683 *
6684 * * `controller` - the directive's required controller instance(s) - Instances are shared
6685 * among all directives, which allows the directives to use the controllers as a communication
6686 * channel. The exact value depends on the directive's `require` property:
6687 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6688 * * `string`: the controller instance
6689 * * `array`: array of controller instances
6690 *
6691 * If a required controller cannot be found, and it is optional, the instance is `null`,
6692 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6693 *
6694 * Note that you can also require the directive's own controller - it will be made available like
6695 * any other controller.
6696 *
6697 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6698 * This is the same as the `$transclude`
6699 * parameter of directive controllers, see there for details.
6700 * `function([scope], cloneLinkingFn, futureParentElement)`.
6701 *
6702 * #### Pre-linking function
6703 *
6704 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6705 * compiler linking function will fail to locate the correct elements for linking.
6706 *
6707 * #### Post-linking function
6708 *
6709 * Executed after the child elements are linked.
6710 *
6711 * Note that child elements that contain `templateUrl` directives will not have been compiled
6712 * and linked since they are waiting for their template to load asynchronously and their own
6713 * compilation and linking has been suspended until that occurs.
6714 *
6715 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6716 * for their async templates to be resolved.
6717 *
6718 *
6719 * ### Transclusion
6720 *
6721 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6722 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6723 * scope from where they were taken.
6724 *
6725 * Transclusion is used (often with {@link ngTransclude}) to insert the
6726 * original contents of a directive's element into a specified place in the template of the directive.
6727 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6728 * content has access to the properties on the scope from which it was taken, even if the directive
6729 * has isolated scope.
6730 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6731 *
6732 * This makes it possible for the widget to have private state for its template, while the transcluded
6733 * content has access to its originating scope.
6734 *
6735 * <div class="alert alert-warning">
6736 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6737 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6738 * Testing Transclusion Directives}.
6739 * </div>
6740 *
6741 * #### Transclusion Functions
6742 *
6743 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6744 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6745 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6746 *
6747 * <div class="alert alert-info">
6748 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6749 * ngTransclude will deal with it for us.
6750 * </div>
6751 *
6752 * If you want to manually control the insertion and removal of the transcluded content in your directive
6753 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6754 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6755 *
6756 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6757 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6758 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6759 *
6760 * <div class="alert alert-info">
6761 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6762 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6763 * </div>
6764 *
6765 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6766 * attach function**:
6767 *
6768 * ```js
6769 * var transcludedContent, transclusionScope;
6770 *
6771 * $transclude(function(clone, scope) {
6772 * element.append(clone);
6773 * transcludedContent = clone;
6774 * transclusionScope = scope;
6775 * });
6776 * ```
6777 *
6778 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6779 * associated transclusion scope:
6780 *
6781 * ```js
6782 * transcludedContent.remove();
6783 * transclusionScope.$destroy();
6784 * ```
6785 *
6786 * <div class="alert alert-info">
6787 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6788 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6789 * then you are also responsible for calling `$destroy` on the transclusion scope.
6790 * </div>
6791 *
6792 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6793 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6794 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6795 *
6796 *
6797 * #### Transclusion Scopes
6798 *
6799 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6800 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6801 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6802 * was taken.
6803 *
6804 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6805 * like this:
6806 *
6807 * ```html
6808 * <div ng-app>
6809 * <div isolate>
6810 * <div transclusion>
6811 * </div>
6812 * </div>
6813 * </div>
6814 * ```
6815 *
6816 * The `$parent` scope hierarchy will look like this:
6817 *
6818 * ```
6819 * - $rootScope
6820 * - isolate
6821 * - transclusion
6822 * ```
6823 *
6824 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6825 *
6826 * ```
6827 * - $rootScope
6828 * - transclusion
6829 * - isolate
6830 * ```
6831 *
6832 *
6833 * ### Attributes
6834 *
6835 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6836 * `link()` or `compile()` functions. It has a variety of uses.
6837 *
6838 * accessing *Normalized attribute names:*
6839 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6840 * the attributes object allows for normalized access to
6841 * the attributes.
6842 *
6843 * * *Directive inter-communication:* All directives share the same instance of the attributes
6844 * object which allows the directives to use the attributes object as inter directive
6845 * communication.
6846 *
6847 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6848 * allowing other directives to read the interpolated value.
6849 *
6850 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6851 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6852 * the only way to easily get the actual value because during the linking phase the interpolation
6853 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6854 *
6855 * ```js
6856 * function linkingFn(scope, elm, attrs, ctrl) {
6857 * // get the attribute value
6858 * console.log(attrs.ngModel);
6859 *
6860 * // change the attribute
6861 * attrs.$set('ngModel', 'new value');
6862 *
6863 * // observe changes to interpolated attribute
6864 * attrs.$observe('ngModel', function(value) {
6865 * console.log('ngModel has changed value to ' + value);
6866 * });
6867 * }
6868 * ```
6869 *
6870 * ## Example
6871 *
6872 * <div class="alert alert-warning">
6873 * **Note**: Typically directives are registered with `module.directive`. The example below is
6874 * to illustrate how `$compile` works.
6875 * </div>
6876 *
6877 <example module="compileExample">
6878 <file name="index.html">
6879 <script>
6880 angular.module('compileExample', [], function($compileProvider) {
6881 // configure new 'compile' directive by passing a directive
6882 // factory function. The factory function injects the '$compile'
6883 $compileProvider.directive('compile', function($compile) {
6884 // directive factory creates a link function
6885 return function(scope, element, attrs) {
6886 scope.$watch(
6887 function(scope) {
6888 // watch the 'compile' expression for changes
6889 return scope.$eval(attrs.compile);
6890 },
6891 function(value) {
6892 // when the 'compile' expression changes
6893 // assign it into the current DOM
6894 element.html(value);
6895
6896 // compile the new DOM and link it to the current
6897 // scope.
6898 // NOTE: we only compile .childNodes so that
6899 // we don't get into infinite loop compiling ourselves
6900 $compile(element.contents())(scope);
6901 }
6902 );
6903 };
6904 });
6905 })
6906 .controller('GreeterController', ['$scope', function($scope) {
6907 $scope.name = 'Angular';
6908 $scope.html = 'Hello {{name}}';
6909 }]);
6910 </script>
6911 <div ng-controller="GreeterController">
6912 <input ng-model="name"> <br/>
6913 <textarea ng-model="html"></textarea> <br/>
6914 <div compile="html"></div>
6915 </div>
6916 </file>
6917 <file name="protractor.js" type="protractor">
6918 it('should auto compile', function() {
6919 var textarea = $('textarea');
6920 var output = $('div[compile]');
6921 // The initial state reads 'Hello Angular'.
6922 expect(output.getText()).toBe('Hello Angular');
6923 textarea.clear();
6924 textarea.sendKeys('{{name}}!');
6925 expect(output.getText()).toBe('Angular!');
6926 });
6927 </file>
6928 </example>
6929
6930 *
6931 *
6932 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
6933 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
6934 *
6935 * <div class="alert alert-danger">
6936 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
6937 * e.g. will not use the right outer scope. Please pass the transclude function as a
6938 * `parentBoundTranscludeFn` to the link function instead.
6939 * </div>
6940 *
6941 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
6942 * root element(s), not their children)
6943 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
6944 * (a DOM element/tree) to a scope. Where:
6945 *
6946 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
6947 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
6948 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
6949 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
6950 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
6951 *
6952 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
6953 * * `scope` - is the current scope with which the linking function is working with.
6954 *
6955 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
6956 * keys may be used to control linking behavior:
6957 *
6958 * * `parentBoundTranscludeFn` - the transclude function made available to
6959 * directives; if given, it will be passed through to the link functions of
6960 * directives found in `element` during compilation.
6961 * * `transcludeControllers` - an object hash with keys that map controller names
6962 * to controller instances; if given, it will make the controllers
6963 * available to directives.
6964 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
6965 * the cloned elements; only needed for transcludes that are allowed to contain non html
6966 * elements (e.g. SVG elements). See also the directive.controller property.
6967 *
6968 * Calling the linking function returns the element of the template. It is either the original
6969 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
6970 *
6971 * After linking the view is not updated until after a call to $digest which typically is done by
6972 * Angular automatically.
6973 *
6974 * If you need access to the bound view, there are two ways to do it:
6975 *
6976 * - If you are not asking the linking function to clone the template, create the DOM element(s)
6977 * before you send them to the compiler and keep this reference around.
6978 * ```js
6979 * var element = $compile('<p>{{total}}</p>')(scope);
6980 * ```
6981 *
6982 * - if on the other hand, you need the element to be cloned, the view reference from the original
6983 * example would not point to the clone, but rather to the original template that was cloned. In
6984 * this case, you can access the clone via the cloneAttachFn:
6985 * ```js
6986 * var templateElement = angular.element('<p>{{total}}</p>'),
6987 * scope = ....;
6988 *
6989 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
6990 * //attach the clone to DOM document at the right place
6991 * });
6992 *
6993 * //now we have reference to the cloned DOM via `clonedElement`
6994 * ```
6995 *
6996 *
6997 * For information on how the compiler works, see the
6998 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
6999 */
7000
7001var $compileMinErr = minErr('$compile');
7002
7003/**
7004 * @ngdoc provider
7005 * @name $compileProvider
7006 *
7007 * @description
7008 */
7009$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7010function $CompileProvider($provide, $$sanitizeUriProvider) {
7011 var hasDirectives = {},
7012 Suffix = 'Directive',
7013 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7014 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7015 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7016 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7017
7018 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7019 // The assumption is that future DOM event attribute names will begin with
7020 // 'on' and be composed of only English letters.
7021 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7022
7023 function parseIsolateBindings(scope, directiveName, isController) {
7024 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7025
7026 var bindings = {};
7027
7028 forEach(scope, function(definition, scopeName) {
7029 var match = definition.match(LOCAL_REGEXP);
7030
7031 if (!match) {
7032 throw $compileMinErr('iscp',
7033 "Invalid {3} for directive '{0}'." +
7034 " Definition: {... {1}: '{2}' ...}",
7035 directiveName, scopeName, definition,
7036 (isController ? "controller bindings definition" :
7037 "isolate scope definition"));
7038 }
7039
7040 bindings[scopeName] = {
7041 mode: match[1][0],
7042 collection: match[2] === '*',
7043 optional: match[3] === '?',
7044 attrName: match[4] || scopeName
7045 };
7046 });
7047
7048 return bindings;
7049 }
7050
7051 function parseDirectiveBindings(directive, directiveName) {
7052 var bindings = {
7053 isolateScope: null,
7054 bindToController: null
7055 };
7056 if (isObject(directive.scope)) {
7057 if (directive.bindToController === true) {
7058 bindings.bindToController = parseIsolateBindings(directive.scope,
7059 directiveName, true);
7060 bindings.isolateScope = {};
7061 } else {
7062 bindings.isolateScope = parseIsolateBindings(directive.scope,
7063 directiveName, false);
7064 }
7065 }
7066 if (isObject(directive.bindToController)) {
7067 bindings.bindToController =
7068 parseIsolateBindings(directive.bindToController, directiveName, true);
7069 }
7070 if (isObject(bindings.bindToController)) {
7071 var controller = directive.controller;
7072 var controllerAs = directive.controllerAs;
7073 if (!controller) {
7074 // There is no controller, there may or may not be a controllerAs property
7075 throw $compileMinErr('noctrl',
7076 "Cannot bind to controller without directive '{0}'s controller.",
7077 directiveName);
7078 } else if (!identifierForController(controller, controllerAs)) {
7079 // There is a controller, but no identifier or controllerAs property
7080 throw $compileMinErr('noident',
7081 "Cannot bind to controller without identifier for directive '{0}'.",
7082 directiveName);
7083 }
7084 }
7085 return bindings;
7086 }
7087
7088 function assertValidDirectiveName(name) {
7089 var letter = name.charAt(0);
7090 if (!letter || letter !== lowercase(letter)) {
7091 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7092 }
7093 if (name !== name.trim()) {
7094 throw $compileMinErr('baddir',
7095 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7096 name);
7097 }
7098 }
7099
7100 /**
7101 * @ngdoc method
7102 * @name $compileProvider#directive
7103 * @kind function
7104 *
7105 * @description
7106 * Register a new directive with the compiler.
7107 *
7108 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7109 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7110 * names and the values are the factories.
7111 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7112 * {@link guide/directive} for more info.
7113 * @returns {ng.$compileProvider} Self for chaining.
7114 */
7115 this.directive = function registerDirective(name, directiveFactory) {
7116 assertNotHasOwnProperty(name, 'directive');
7117 if (isString(name)) {
7118 assertValidDirectiveName(name);
7119 assertArg(directiveFactory, 'directiveFactory');
7120 if (!hasDirectives.hasOwnProperty(name)) {
7121 hasDirectives[name] = [];
7122 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7123 function($injector, $exceptionHandler) {
7124 var directives = [];
7125 forEach(hasDirectives[name], function(directiveFactory, index) {
7126 try {
7127 var directive = $injector.invoke(directiveFactory);
7128 if (isFunction(directive)) {
7129 directive = { compile: valueFn(directive) };
7130 } else if (!directive.compile && directive.link) {
7131 directive.compile = valueFn(directive.link);
7132 }
7133 directive.priority = directive.priority || 0;
7134 directive.index = index;
7135 directive.name = directive.name || name;
7136 directive.require = directive.require || (directive.controller && directive.name);
7137 directive.restrict = directive.restrict || 'EA';
7138 var bindings = directive.$$bindings =
7139 parseDirectiveBindings(directive, directive.name);
7140 if (isObject(bindings.isolateScope)) {
7141 directive.$$isolateBindings = bindings.isolateScope;
7142 }
7143 directive.$$moduleName = directiveFactory.$$moduleName;
7144 directives.push(directive);
7145 } catch (e) {
7146 $exceptionHandler(e);
7147 }
7148 });
7149 return directives;
7150 }]);
7151 }
7152 hasDirectives[name].push(directiveFactory);
7153 } else {
7154 forEach(name, reverseParams(registerDirective));
7155 }
7156 return this;
7157 };
7158
7159
7160 /**
7161 * @ngdoc method
7162 * @name $compileProvider#aHrefSanitizationWhitelist
7163 * @kind function
7164 *
7165 * @description
7166 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7167 * urls during a[href] sanitization.
7168 *
7169 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7170 *
7171 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7172 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7173 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7174 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7175 *
7176 * @param {RegExp=} regexp New regexp to whitelist urls with.
7177 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7178 * chaining otherwise.
7179 */
7180 this.aHrefSanitizationWhitelist = function(regexp) {
7181 if (isDefined(regexp)) {
7182 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7183 return this;
7184 } else {
7185 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7186 }
7187 };
7188
7189
7190 /**
7191 * @ngdoc method
7192 * @name $compileProvider#imgSrcSanitizationWhitelist
7193 * @kind function
7194 *
7195 * @description
7196 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7197 * urls during img[src] sanitization.
7198 *
7199 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7200 *
7201 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7202 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7203 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7204 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7205 *
7206 * @param {RegExp=} regexp New regexp to whitelist urls with.
7207 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7208 * chaining otherwise.
7209 */
7210 this.imgSrcSanitizationWhitelist = function(regexp) {
7211 if (isDefined(regexp)) {
7212 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7213 return this;
7214 } else {
7215 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7216 }
7217 };
7218
7219 /**
7220 * @ngdoc method
7221 * @name $compileProvider#debugInfoEnabled
7222 *
7223 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7224 * current debugInfoEnabled state
7225 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7226 *
7227 * @kind function
7228 *
7229 * @description
7230 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7231 * binding information and a reference to the current scope on to DOM elements.
7232 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7233 * * `ng-binding` CSS class
7234 * * `$binding` data property containing an array of the binding expressions
7235 *
7236 * You may want to disable this in production for a significant performance boost. See
7237 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7238 *
7239 * The default value is true.
7240 */
7241 var debugInfoEnabled = true;
7242 this.debugInfoEnabled = function(enabled) {
7243 if (isDefined(enabled)) {
7244 debugInfoEnabled = enabled;
7245 return this;
7246 }
7247 return debugInfoEnabled;
7248 };
7249
7250 this.$get = [
7251 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7252 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7253 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7254 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7255
7256 var Attributes = function(element, attributesToCopy) {
7257 if (attributesToCopy) {
7258 var keys = Object.keys(attributesToCopy);
7259 var i, l, key;
7260
7261 for (i = 0, l = keys.length; i < l; i++) {
7262 key = keys[i];
7263 this[key] = attributesToCopy[key];
7264 }
7265 } else {
7266 this.$attr = {};
7267 }
7268
7269 this.$$element = element;
7270 };
7271
7272 Attributes.prototype = {
7273 /**
7274 * @ngdoc method
7275 * @name $compile.directive.Attributes#$normalize
7276 * @kind function
7277 *
7278 * @description
7279 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7280 * `data-`) to its normalized, camelCase form.
7281 *
7282 * Also there is special case for Moz prefix starting with upper case letter.
7283 *
7284 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7285 *
7286 * @param {string} name Name to normalize
7287 */
7288 $normalize: directiveNormalize,
7289
7290
7291 /**
7292 * @ngdoc method
7293 * @name $compile.directive.Attributes#$addClass
7294 * @kind function
7295 *
7296 * @description
7297 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7298 * are enabled then an animation will be triggered for the class addition.
7299 *
7300 * @param {string} classVal The className value that will be added to the element
7301 */
7302 $addClass: function(classVal) {
7303 if (classVal && classVal.length > 0) {
7304 $animate.addClass(this.$$element, classVal);
7305 }
7306 },
7307
7308 /**
7309 * @ngdoc method
7310 * @name $compile.directive.Attributes#$removeClass
7311 * @kind function
7312 *
7313 * @description
7314 * Removes the CSS class value specified by the classVal parameter from the element. If
7315 * animations are enabled then an animation will be triggered for the class removal.
7316 *
7317 * @param {string} classVal The className value that will be removed from the element
7318 */
7319 $removeClass: function(classVal) {
7320 if (classVal && classVal.length > 0) {
7321 $animate.removeClass(this.$$element, classVal);
7322 }
7323 },
7324
7325 /**
7326 * @ngdoc method
7327 * @name $compile.directive.Attributes#$updateClass
7328 * @kind function
7329 *
7330 * @description
7331 * Adds and removes the appropriate CSS class values to the element based on the difference
7332 * between the new and old CSS class values (specified as newClasses and oldClasses).
7333 *
7334 * @param {string} newClasses The current CSS className value
7335 * @param {string} oldClasses The former CSS className value
7336 */
7337 $updateClass: function(newClasses, oldClasses) {
7338 var toAdd = tokenDifference(newClasses, oldClasses);
7339 if (toAdd && toAdd.length) {
7340 $animate.addClass(this.$$element, toAdd);
7341 }
7342
7343 var toRemove = tokenDifference(oldClasses, newClasses);
7344 if (toRemove && toRemove.length) {
7345 $animate.removeClass(this.$$element, toRemove);
7346 }
7347 },
7348
7349 /**
7350 * Set a normalized attribute on the element in a way such that all directives
7351 * can share the attribute. This function properly handles boolean attributes.
7352 * @param {string} key Normalized key. (ie ngAttribute)
7353 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7354 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7355 * Defaults to true.
7356 * @param {string=} attrName Optional none normalized name. Defaults to key.
7357 */
7358 $set: function(key, value, writeAttr, attrName) {
7359 // TODO: decide whether or not to throw an error if "class"
7360 //is set through this function since it may cause $updateClass to
7361 //become unstable.
7362
7363 var node = this.$$element[0],
7364 booleanKey = getBooleanAttrName(node, key),
7365 aliasedKey = getAliasedAttrName(key),
7366 observer = key,
7367 nodeName;
7368
7369 if (booleanKey) {
7370 this.$$element.prop(key, value);
7371 attrName = booleanKey;
7372 } else if (aliasedKey) {
7373 this[aliasedKey] = value;
7374 observer = aliasedKey;
7375 }
7376
7377 this[key] = value;
7378
7379 // translate normalized key to actual key
7380 if (attrName) {
7381 this.$attr[key] = attrName;
7382 } else {
7383 attrName = this.$attr[key];
7384 if (!attrName) {
7385 this.$attr[key] = attrName = snake_case(key, '-');
7386 }
7387 }
7388
7389 nodeName = nodeName_(this.$$element);
7390
7391 if ((nodeName === 'a' && key === 'href') ||
7392 (nodeName === 'img' && key === 'src')) {
7393 // sanitize a[href] and img[src] values
7394 this[key] = value = $$sanitizeUri(value, key === 'src');
7395 } else if (nodeName === 'img' && key === 'srcset') {
7396 // sanitize img[srcset] values
7397 var result = "";
7398
7399 // first check if there are spaces because it's not the same pattern
7400 var trimmedSrcset = trim(value);
7401 // ( 999x ,| 999w ,| ,|, )
7402 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7403 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7404
7405 // split srcset into tuple of uri and descriptor except for the last item
7406 var rawUris = trimmedSrcset.split(pattern);
7407
7408 // for each tuples
7409 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7410 for (var i = 0; i < nbrUrisWith2parts; i++) {
7411 var innerIdx = i * 2;
7412 // sanitize the uri
7413 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7414 // add the descriptor
7415 result += (" " + trim(rawUris[innerIdx + 1]));
7416 }
7417
7418 // split the last item into uri and descriptor
7419 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7420
7421 // sanitize the last uri
7422 result += $$sanitizeUri(trim(lastTuple[0]), true);
7423
7424 // and add the last descriptor if any
7425 if (lastTuple.length === 2) {
7426 result += (" " + trim(lastTuple[1]));
7427 }
7428 this[key] = value = result;
7429 }
7430
7431 if (writeAttr !== false) {
7432 if (value === null || isUndefined(value)) {
7433 this.$$element.removeAttr(attrName);
7434 } else {
7435 this.$$element.attr(attrName, value);
7436 }
7437 }
7438
7439 // fire observers
7440 var $$observers = this.$$observers;
7441 $$observers && forEach($$observers[observer], function(fn) {
7442 try {
7443 fn(value);
7444 } catch (e) {
7445 $exceptionHandler(e);
7446 }
7447 });
7448 },
7449
7450
7451 /**
7452 * @ngdoc method
7453 * @name $compile.directive.Attributes#$observe
7454 * @kind function
7455 *
7456 * @description
7457 * Observes an interpolated attribute.
7458 *
7459 * The observer function will be invoked once during the next `$digest` following
7460 * compilation. The observer is then invoked whenever the interpolated value
7461 * changes.
7462 *
7463 * @param {string} key Normalized key. (ie ngAttribute) .
7464 * @param {function(interpolatedValue)} fn Function that will be called whenever
7465 the interpolated value of the attribute changes.
7466 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7467 * @returns {function()} Returns a deregistration function for this observer.
7468 */
7469 $observe: function(key, fn) {
7470 var attrs = this,
7471 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7472 listeners = ($$observers[key] || ($$observers[key] = []));
7473
7474 listeners.push(fn);
7475 $rootScope.$evalAsync(function() {
7476 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7477 // no one registered attribute interpolation function, so lets call it manually
7478 fn(attrs[key]);
7479 }
7480 });
7481
7482 return function() {
7483 arrayRemove(listeners, fn);
7484 };
7485 }
7486 };
7487
7488
7489 function safeAddClass($element, className) {
7490 try {
7491 $element.addClass(className);
7492 } catch (e) {
7493 // ignore, since it means that we are trying to set class on
7494 // SVG element, where class name is read-only.
7495 }
7496 }
7497
7498
7499 var startSymbol = $interpolate.startSymbol(),
7500 endSymbol = $interpolate.endSymbol(),
7501 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7502 ? identity
7503 : function denormalizeTemplate(template) {
7504 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7505 },
7506 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7507 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7508
7509 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7510 var bindings = $element.data('$binding') || [];
7511
7512 if (isArray(binding)) {
7513 bindings = bindings.concat(binding);
7514 } else {
7515 bindings.push(binding);
7516 }
7517
7518 $element.data('$binding', bindings);
7519 } : noop;
7520
7521 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7522 safeAddClass($element, 'ng-binding');
7523 } : noop;
7524
7525 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7526 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7527 $element.data(dataName, scope);
7528 } : noop;
7529
7530 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7531 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7532 } : noop;
7533
7534 return compile;
7535
7536 //================================
7537
7538 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7539 previousCompileContext) {
7540 if (!($compileNodes instanceof jqLite)) {
7541 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7542 // modify it.
7543 $compileNodes = jqLite($compileNodes);
7544 }
7545 // We can not compile top level text elements since text nodes can be merged and we will
7546 // not be able to attach scope data to them, so we will wrap them in <span>
7547 forEach($compileNodes, function(node, index) {
7548 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7549 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7550 }
7551 });
7552 var compositeLinkFn =
7553 compileNodes($compileNodes, transcludeFn, $compileNodes,
7554 maxPriority, ignoreDirective, previousCompileContext);
7555 compile.$$addScopeClass($compileNodes);
7556 var namespace = null;
7557 return function publicLinkFn(scope, cloneConnectFn, options) {
7558 assertArg(scope, 'scope');
7559
7560 if (previousCompileContext && previousCompileContext.needsNewScope) {
7561 // A parent directive did a replace and a directive on this element asked
7562 // for transclusion, which caused us to lose a layer of element on which
7563 // we could hold the new transclusion scope, so we will create it manually
7564 // here.
7565 scope = scope.$parent.$new();
7566 }
7567
7568 options = options || {};
7569 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7570 transcludeControllers = options.transcludeControllers,
7571 futureParentElement = options.futureParentElement;
7572
7573 // When `parentBoundTranscludeFn` is passed, it is a
7574 // `controllersBoundTransclude` function (it was previously passed
7575 // as `transclude` to directive.link) so we must unwrap it to get
7576 // its `boundTranscludeFn`
7577 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7578 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7579 }
7580
7581 if (!namespace) {
7582 namespace = detectNamespaceForChildElements(futureParentElement);
7583 }
7584 var $linkNode;
7585 if (namespace !== 'html') {
7586 // When using a directive with replace:true and templateUrl the $compileNodes
7587 // (or a child element inside of them)
7588 // might change, so we need to recreate the namespace adapted compileNodes
7589 // for call to the link function.
7590 // Note: This will already clone the nodes...
7591 $linkNode = jqLite(
7592 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7593 );
7594 } else if (cloneConnectFn) {
7595 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7596 // and sometimes changes the structure of the DOM.
7597 $linkNode = JQLitePrototype.clone.call($compileNodes);
7598 } else {
7599 $linkNode = $compileNodes;
7600 }
7601
7602 if (transcludeControllers) {
7603 for (var controllerName in transcludeControllers) {
7604 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7605 }
7606 }
7607
7608 compile.$$addScopeInfo($linkNode, scope);
7609
7610 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7611 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7612 return $linkNode;
7613 };
7614 }
7615
7616 function detectNamespaceForChildElements(parentElement) {
7617 // TODO: Make this detect MathML as well...
7618 var node = parentElement && parentElement[0];
7619 if (!node) {
7620 return 'html';
7621 } else {
7622 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7623 }
7624 }
7625
7626 /**
7627 * Compile function matches each node in nodeList against the directives. Once all directives
7628 * for a particular node are collected their compile functions are executed. The compile
7629 * functions return values - the linking functions - are combined into a composite linking
7630 * function, which is the a linking function for the node.
7631 *
7632 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7633 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7634 * scope argument is auto-generated to the new child of the transcluded parent scope.
7635 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7636 * the rootElement must be set the jqLite collection of the compile root. This is
7637 * needed so that the jqLite collection items can be replaced with widgets.
7638 * @param {number=} maxPriority Max directive priority.
7639 * @returns {Function} A composite linking function of all of the matched directives or null.
7640 */
7641 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7642 previousCompileContext) {
7643 var linkFns = [],
7644 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7645
7646 for (var i = 0; i < nodeList.length; i++) {
7647 attrs = new Attributes();
7648
7649 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7650 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7651 ignoreDirective);
7652
7653 nodeLinkFn = (directives.length)
7654 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7655 null, [], [], previousCompileContext)
7656 : null;
7657
7658 if (nodeLinkFn && nodeLinkFn.scope) {
7659 compile.$$addScopeClass(attrs.$$element);
7660 }
7661
7662 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7663 !(childNodes = nodeList[i].childNodes) ||
7664 !childNodes.length)
7665 ? null
7666 : compileNodes(childNodes,
7667 nodeLinkFn ? (
7668 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7669 && nodeLinkFn.transclude) : transcludeFn);
7670
7671 if (nodeLinkFn || childLinkFn) {
7672 linkFns.push(i, nodeLinkFn, childLinkFn);
7673 linkFnFound = true;
7674 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7675 }
7676
7677 //use the previous context only for the first element in the virtual group
7678 previousCompileContext = null;
7679 }
7680
7681 // return a linking function if we have found anything, null otherwise
7682 return linkFnFound ? compositeLinkFn : null;
7683
7684 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7685 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7686 var stableNodeList;
7687
7688
7689 if (nodeLinkFnFound) {
7690 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7691 // offsets don't get screwed up
7692 var nodeListLength = nodeList.length;
7693 stableNodeList = new Array(nodeListLength);
7694
7695 // create a sparse array by only copying the elements which have a linkFn
7696 for (i = 0; i < linkFns.length; i+=3) {
7697 idx = linkFns[i];
7698 stableNodeList[idx] = nodeList[idx];
7699 }
7700 } else {
7701 stableNodeList = nodeList;
7702 }
7703
7704 for (i = 0, ii = linkFns.length; i < ii;) {
7705 node = stableNodeList[linkFns[i++]];
7706 nodeLinkFn = linkFns[i++];
7707 childLinkFn = linkFns[i++];
7708
7709 if (nodeLinkFn) {
7710 if (nodeLinkFn.scope) {
7711 childScope = scope.$new();
7712 compile.$$addScopeInfo(jqLite(node), childScope);
7713 } else {
7714 childScope = scope;
7715 }
7716
7717 if (nodeLinkFn.transcludeOnThisElement) {
7718 childBoundTranscludeFn = createBoundTranscludeFn(
7719 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7720
7721 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7722 childBoundTranscludeFn = parentBoundTranscludeFn;
7723
7724 } else if (!parentBoundTranscludeFn && transcludeFn) {
7725 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7726
7727 } else {
7728 childBoundTranscludeFn = null;
7729 }
7730
7731 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7732
7733 } else if (childLinkFn) {
7734 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7735 }
7736 }
7737 }
7738 }
7739
7740 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7741
7742 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7743
7744 if (!transcludedScope) {
7745 transcludedScope = scope.$new(false, containingScope);
7746 transcludedScope.$$transcluded = true;
7747 }
7748
7749 return transcludeFn(transcludedScope, cloneFn, {
7750 parentBoundTranscludeFn: previousBoundTranscludeFn,
7751 transcludeControllers: controllers,
7752 futureParentElement: futureParentElement
7753 });
7754 };
7755
7756 return boundTranscludeFn;
7757 }
7758
7759 /**
7760 * Looks for directives on the given node and adds them to the directive collection which is
7761 * sorted.
7762 *
7763 * @param node Node to search.
7764 * @param directives An array to which the directives are added to. This array is sorted before
7765 * the function returns.
7766 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7767 * @param {number=} maxPriority Max directive priority.
7768 */
7769 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7770 var nodeType = node.nodeType,
7771 attrsMap = attrs.$attr,
7772 match,
7773 className;
7774
7775 switch (nodeType) {
7776 case NODE_TYPE_ELEMENT: /* Element */
7777 // use the node name: <directive>
7778 addDirective(directives,
7779 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7780
7781 // iterate over the attributes
7782 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7783 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7784 var attrStartName = false;
7785 var attrEndName = false;
7786
7787 attr = nAttrs[j];
7788 name = attr.name;
7789 value = trim(attr.value);
7790
7791 // support ngAttr attribute binding
7792 ngAttrName = directiveNormalize(name);
7793 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7794 name = name.replace(PREFIX_REGEXP, '')
7795 .substr(8).replace(/_(.)/g, function(match, letter) {
7796 return letter.toUpperCase();
7797 });
7798 }
7799
7800 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7801 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7802 attrStartName = name;
7803 attrEndName = name.substr(0, name.length - 5) + 'end';
7804 name = name.substr(0, name.length - 6);
7805 }
7806
7807 nName = directiveNormalize(name.toLowerCase());
7808 attrsMap[nName] = name;
7809 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7810 attrs[nName] = value;
7811 if (getBooleanAttrName(node, nName)) {
7812 attrs[nName] = true; // presence means true
7813 }
7814 }
7815 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7816 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7817 attrEndName);
7818 }
7819
7820 // use class as directive
7821 className = node.className;
7822 if (isObject(className)) {
7823 // Maybe SVGAnimatedString
7824 className = className.animVal;
7825 }
7826 if (isString(className) && className !== '') {
7827 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7828 nName = directiveNormalize(match[2]);
7829 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7830 attrs[nName] = trim(match[3]);
7831 }
7832 className = className.substr(match.index + match[0].length);
7833 }
7834 }
7835 break;
7836 case NODE_TYPE_TEXT: /* Text Node */
7837 if (msie === 11) {
7838 // Workaround for #11781
7839 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7840 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7841 node.parentNode.removeChild(node.nextSibling);
7842 }
7843 }
7844 addTextInterpolateDirective(directives, node.nodeValue);
7845 break;
7846 case NODE_TYPE_COMMENT: /* Comment */
7847 try {
7848 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7849 if (match) {
7850 nName = directiveNormalize(match[1]);
7851 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7852 attrs[nName] = trim(match[2]);
7853 }
7854 }
7855 } catch (e) {
7856 // turns out that under some circumstances IE9 throws errors when one attempts to read
7857 // comment's node value.
7858 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7859 }
7860 break;
7861 }
7862
7863 directives.sort(byPriority);
7864 return directives;
7865 }
7866
7867 /**
7868 * Given a node with an directive-start it collects all of the siblings until it finds
7869 * directive-end.
7870 * @param node
7871 * @param attrStart
7872 * @param attrEnd
7873 * @returns {*}
7874 */
7875 function groupScan(node, attrStart, attrEnd) {
7876 var nodes = [];
7877 var depth = 0;
7878 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7879 do {
7880 if (!node) {
7881 throw $compileMinErr('uterdir',
7882 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7883 attrStart, attrEnd);
7884 }
7885 if (node.nodeType == NODE_TYPE_ELEMENT) {
7886 if (node.hasAttribute(attrStart)) depth++;
7887 if (node.hasAttribute(attrEnd)) depth--;
7888 }
7889 nodes.push(node);
7890 node = node.nextSibling;
7891 } while (depth > 0);
7892 } else {
7893 nodes.push(node);
7894 }
7895
7896 return jqLite(nodes);
7897 }
7898
7899 /**
7900 * Wrapper for linking function which converts normal linking function into a grouped
7901 * linking function.
7902 * @param linkFn
7903 * @param attrStart
7904 * @param attrEnd
7905 * @returns {Function}
7906 */
7907 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7908 return function(scope, element, attrs, controllers, transcludeFn) {
7909 element = groupScan(element[0], attrStart, attrEnd);
7910 return linkFn(scope, element, attrs, controllers, transcludeFn);
7911 };
7912 }
7913
7914 /**
7915 * Once the directives have been collected, their compile functions are executed. This method
7916 * is responsible for inlining directive templates as well as terminating the application
7917 * of the directives if the terminal directive has been reached.
7918 *
7919 * @param {Array} directives Array of collected directives to execute their compile function.
7920 * this needs to be pre-sorted by priority order.
7921 * @param {Node} compileNode The raw DOM node to apply the compile functions to
7922 * @param {Object} templateAttrs The shared attribute function
7923 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7924 * scope argument is auto-generated to the new
7925 * child of the transcluded parent scope.
7926 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
7927 * argument has the root jqLite array so that we can replace nodes
7928 * on it.
7929 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
7930 * compiling the transclusion.
7931 * @param {Array.<Function>} preLinkFns
7932 * @param {Array.<Function>} postLinkFns
7933 * @param {Object} previousCompileContext Context used for previous compilation of the current
7934 * node
7935 * @returns {Function} linkFn
7936 */
7937 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
7938 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
7939 previousCompileContext) {
7940 previousCompileContext = previousCompileContext || {};
7941
7942 var terminalPriority = -Number.MAX_VALUE,
7943 newScopeDirective = previousCompileContext.newScopeDirective,
7944 controllerDirectives = previousCompileContext.controllerDirectives,
7945 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
7946 templateDirective = previousCompileContext.templateDirective,
7947 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
7948 hasTranscludeDirective = false,
7949 hasTemplate = false,
7950 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
7951 $compileNode = templateAttrs.$$element = jqLite(compileNode),
7952 directive,
7953 directiveName,
7954 $template,
7955 replaceDirective = originalReplaceDirective,
7956 childTranscludeFn = transcludeFn,
7957 linkFn,
7958 directiveValue;
7959
7960 // executes all directives on the current element
7961 for (var i = 0, ii = directives.length; i < ii; i++) {
7962 directive = directives[i];
7963 var attrStart = directive.$$start;
7964 var attrEnd = directive.$$end;
7965
7966 // collect multiblock sections
7967 if (attrStart) {
7968 $compileNode = groupScan(compileNode, attrStart, attrEnd);
7969 }
7970 $template = undefined;
7971
7972 if (terminalPriority > directive.priority) {
7973 break; // prevent further processing of directives
7974 }
7975
7976 if (directiveValue = directive.scope) {
7977
7978 // skip the check for directives with async templates, we'll check the derived sync
7979 // directive when the template arrives
7980 if (!directive.templateUrl) {
7981 if (isObject(directiveValue)) {
7982 // This directive is trying to add an isolated scope.
7983 // Check that there is no scope of any kind already
7984 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
7985 directive, $compileNode);
7986 newIsolateScopeDirective = directive;
7987 } else {
7988 // This directive is trying to add a child scope.
7989 // Check that there is no isolated scope already
7990 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
7991 $compileNode);
7992 }
7993 }
7994
7995 newScopeDirective = newScopeDirective || directive;
7996 }
7997
7998 directiveName = directive.name;
7999
8000 if (!directive.templateUrl && directive.controller) {
8001 directiveValue = directive.controller;
8002 controllerDirectives = controllerDirectives || createMap();
8003 assertNoDuplicate("'" + directiveName + "' controller",
8004 controllerDirectives[directiveName], directive, $compileNode);
8005 controllerDirectives[directiveName] = directive;
8006 }
8007
8008 if (directiveValue = directive.transclude) {
8009 hasTranscludeDirective = true;
8010
8011 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8012 // This option should only be used by directives that know how to safely handle element transclusion,
8013 // where the transcluded nodes are added or replaced after linking.
8014 if (!directive.$$tlb) {
8015 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8016 nonTlbTranscludeDirective = directive;
8017 }
8018
8019 if (directiveValue == 'element') {
8020 hasElementTranscludeDirective = true;
8021 terminalPriority = directive.priority;
8022 $template = $compileNode;
8023 $compileNode = templateAttrs.$$element =
8024 jqLite(document.createComment(' ' + directiveName + ': ' +
8025 templateAttrs[directiveName] + ' '));
8026 compileNode = $compileNode[0];
8027 replaceWith(jqCollection, sliceArgs($template), compileNode);
8028
8029 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8030 replaceDirective && replaceDirective.name, {
8031 // Don't pass in:
8032 // - controllerDirectives - otherwise we'll create duplicates controllers
8033 // - newIsolateScopeDirective or templateDirective - combining templates with
8034 // element transclusion doesn't make sense.
8035 //
8036 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8037 // on the same element more than once.
8038 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8039 });
8040 } else {
8041 $template = jqLite(jqLiteClone(compileNode)).contents();
8042 $compileNode.empty(); // clear contents
8043 childTranscludeFn = compile($template, transcludeFn, undefined,
8044 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8045 }
8046 }
8047
8048 if (directive.template) {
8049 hasTemplate = true;
8050 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8051 templateDirective = directive;
8052
8053 directiveValue = (isFunction(directive.template))
8054 ? directive.template($compileNode, templateAttrs)
8055 : directive.template;
8056
8057 directiveValue = denormalizeTemplate(directiveValue);
8058
8059 if (directive.replace) {
8060 replaceDirective = directive;
8061 if (jqLiteIsTextNode(directiveValue)) {
8062 $template = [];
8063 } else {
8064 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8065 }
8066 compileNode = $template[0];
8067
8068 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8069 throw $compileMinErr('tplrt',
8070 "Template for directive '{0}' must have exactly one root element. {1}",
8071 directiveName, '');
8072 }
8073
8074 replaceWith(jqCollection, $compileNode, compileNode);
8075
8076 var newTemplateAttrs = {$attr: {}};
8077
8078 // combine directives from the original node and from the template:
8079 // - take the array of directives for this element
8080 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8081 // - collect directives from the template and sort them by priority
8082 // - combine directives as: processed + template + unprocessed
8083 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8084 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8085
8086 if (newIsolateScopeDirective || newScopeDirective) {
8087 // The original directive caused the current element to be replaced but this element
8088 // also needs to have a new scope, so we need to tell the template directives
8089 // that they would need to get their scope from further up, if they require transclusion
8090 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8091 }
8092 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8093 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8094
8095 ii = directives.length;
8096 } else {
8097 $compileNode.html(directiveValue);
8098 }
8099 }
8100
8101 if (directive.templateUrl) {
8102 hasTemplate = true;
8103 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8104 templateDirective = directive;
8105
8106 if (directive.replace) {
8107 replaceDirective = directive;
8108 }
8109
8110 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8111 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8112 controllerDirectives: controllerDirectives,
8113 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8114 newIsolateScopeDirective: newIsolateScopeDirective,
8115 templateDirective: templateDirective,
8116 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8117 });
8118 ii = directives.length;
8119 } else if (directive.compile) {
8120 try {
8121 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8122 if (isFunction(linkFn)) {
8123 addLinkFns(null, linkFn, attrStart, attrEnd);
8124 } else if (linkFn) {
8125 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8126 }
8127 } catch (e) {
8128 $exceptionHandler(e, startingTag($compileNode));
8129 }
8130 }
8131
8132 if (directive.terminal) {
8133 nodeLinkFn.terminal = true;
8134 terminalPriority = Math.max(terminalPriority, directive.priority);
8135 }
8136
8137 }
8138
8139 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8140 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8141 nodeLinkFn.templateOnThisElement = hasTemplate;
8142 nodeLinkFn.transclude = childTranscludeFn;
8143
8144 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8145
8146 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8147 return nodeLinkFn;
8148
8149 ////////////////////
8150
8151 function addLinkFns(pre, post, attrStart, attrEnd) {
8152 if (pre) {
8153 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8154 pre.require = directive.require;
8155 pre.directiveName = directiveName;
8156 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8157 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8158 }
8159 preLinkFns.push(pre);
8160 }
8161 if (post) {
8162 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8163 post.require = directive.require;
8164 post.directiveName = directiveName;
8165 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8166 post = cloneAndAnnotateFn(post, {isolateScope: true});
8167 }
8168 postLinkFns.push(post);
8169 }
8170 }
8171
8172
8173 function getControllers(directiveName, require, $element, elementControllers) {
8174 var value;
8175
8176 if (isString(require)) {
8177 var match = require.match(REQUIRE_PREFIX_REGEXP);
8178 var name = require.substring(match[0].length);
8179 var inheritType = match[1] || match[3];
8180 var optional = match[2] === '?';
8181
8182 //If only parents then start at the parent element
8183 if (inheritType === '^^') {
8184 $element = $element.parent();
8185 //Otherwise attempt getting the controller from elementControllers in case
8186 //the element is transcluded (and has no data) and to avoid .data if possible
8187 } else {
8188 value = elementControllers && elementControllers[name];
8189 value = value && value.instance;
8190 }
8191
8192 if (!value) {
8193 var dataName = '$' + name + 'Controller';
8194 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8195 }
8196
8197 if (!value && !optional) {
8198 throw $compileMinErr('ctreq',
8199 "Controller '{0}', required by directive '{1}', can't be found!",
8200 name, directiveName);
8201 }
8202 } else if (isArray(require)) {
8203 value = [];
8204 for (var i = 0, ii = require.length; i < ii; i++) {
8205 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8206 }
8207 }
8208
8209 return value || null;
8210 }
8211
8212 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8213 var elementControllers = createMap();
8214 for (var controllerKey in controllerDirectives) {
8215 var directive = controllerDirectives[controllerKey];
8216 var locals = {
8217 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8218 $element: $element,
8219 $attrs: attrs,
8220 $transclude: transcludeFn
8221 };
8222
8223 var controller = directive.controller;
8224 if (controller == '@') {
8225 controller = attrs[directive.name];
8226 }
8227
8228 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8229
8230 // For directives with element transclusion the element is a comment,
8231 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8232 // clean up (http://bugs.jquery.com/ticket/8335).
8233 // Instead, we save the controllers for the element in a local hash and attach to .data
8234 // later, once we have the actual element.
8235 elementControllers[directive.name] = controllerInstance;
8236 if (!hasElementTranscludeDirective) {
8237 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8238 }
8239 }
8240 return elementControllers;
8241 }
8242
8243 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8244 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8245 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8246
8247 if (compileNode === linkNode) {
8248 attrs = templateAttrs;
8249 $element = templateAttrs.$$element;
8250 } else {
8251 $element = jqLite(linkNode);
8252 attrs = new Attributes($element, templateAttrs);
8253 }
8254
8255 controllerScope = scope;
8256 if (newIsolateScopeDirective) {
8257 isolateScope = scope.$new(true);
8258 } else if (newScopeDirective) {
8259 controllerScope = scope.$parent;
8260 }
8261
8262 if (boundTranscludeFn) {
8263 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8264 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8265 transcludeFn = controllersBoundTransclude;
8266 transcludeFn.$$boundTransclude = boundTranscludeFn;
8267 }
8268
8269 if (controllerDirectives) {
8270 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8271 }
8272
8273 if (newIsolateScopeDirective) {
8274 // Initialize isolate scope bindings for new isolate scope directive.
8275 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8276 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8277 compile.$$addScopeClass($element, true);
8278 isolateScope.$$isolateBindings =
8279 newIsolateScopeDirective.$$isolateBindings;
8280 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8281 isolateScope.$$isolateBindings,
8282 newIsolateScopeDirective);
8283 if (removeScopeBindingWatches) {
8284 isolateScope.$on('$destroy', removeScopeBindingWatches);
8285 }
8286 }
8287
8288 // Initialize bindToController bindings
8289 for (var name in elementControllers) {
8290 var controllerDirective = controllerDirectives[name];
8291 var controller = elementControllers[name];
8292 var bindings = controllerDirective.$$bindings.bindToController;
8293
8294 if (controller.identifier && bindings) {
8295 removeControllerBindingWatches =
8296 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8297 }
8298
8299 var controllerResult = controller();
8300 if (controllerResult !== controller.instance) {
8301 // If the controller constructor has a return value, overwrite the instance
8302 // from setupControllers
8303 controller.instance = controllerResult;
8304 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8305 removeControllerBindingWatches && removeControllerBindingWatches();
8306 removeControllerBindingWatches =
8307 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8308 }
8309 }
8310
8311 // PRELINKING
8312 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8313 linkFn = preLinkFns[i];
8314 invokeLinkFn(linkFn,
8315 linkFn.isolateScope ? isolateScope : scope,
8316 $element,
8317 attrs,
8318 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8319 transcludeFn
8320 );
8321 }
8322
8323 // RECURSION
8324 // We only pass the isolate scope, if the isolate directive has a template,
8325 // otherwise the child elements do not belong to the isolate directive.
8326 var scopeToChild = scope;
8327 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8328 scopeToChild = isolateScope;
8329 }
8330 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8331
8332 // POSTLINKING
8333 for (i = postLinkFns.length - 1; i >= 0; i--) {
8334 linkFn = postLinkFns[i];
8335 invokeLinkFn(linkFn,
8336 linkFn.isolateScope ? isolateScope : scope,
8337 $element,
8338 attrs,
8339 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8340 transcludeFn
8341 );
8342 }
8343
8344 // This is the function that is injected as `$transclude`.
8345 // Note: all arguments are optional!
8346 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8347 var transcludeControllers;
8348
8349 // No scope passed in:
8350 if (!isScope(scope)) {
8351 futureParentElement = cloneAttachFn;
8352 cloneAttachFn = scope;
8353 scope = undefined;
8354 }
8355
8356 if (hasElementTranscludeDirective) {
8357 transcludeControllers = elementControllers;
8358 }
8359 if (!futureParentElement) {
8360 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8361 }
8362 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8363 }
8364 }
8365 }
8366
8367 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8368 // or child scope created. For instance:
8369 // * if the directive has been pulled into a template because another directive with a higher priority
8370 // asked for element transclusion
8371 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8372 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8373 function markDirectiveScope(directives, isolateScope, newScope) {
8374 for (var j = 0, jj = directives.length; j < jj; j++) {
8375 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8376 }
8377 }
8378
8379 /**
8380 * looks up the directive and decorates it with exception handling and proper parameters. We
8381 * call this the boundDirective.
8382 *
8383 * @param {string} name name of the directive to look up.
8384 * @param {string} location The directive must be found in specific format.
8385 * String containing any of theses characters:
8386 *
8387 * * `E`: element name
8388 * * `A': attribute
8389 * * `C`: class
8390 * * `M`: comment
8391 * @returns {boolean} true if directive was added.
8392 */
8393 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8394 endAttrName) {
8395 if (name === ignoreDirective) return null;
8396 var match = null;
8397 if (hasDirectives.hasOwnProperty(name)) {
8398 for (var directive, directives = $injector.get(name + Suffix),
8399 i = 0, ii = directives.length; i < ii; i++) {
8400 try {
8401 directive = directives[i];
8402 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8403 directive.restrict.indexOf(location) != -1) {
8404 if (startAttrName) {
8405 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8406 }
8407 tDirectives.push(directive);
8408 match = directive;
8409 }
8410 } catch (e) { $exceptionHandler(e); }
8411 }
8412 }
8413 return match;
8414 }
8415
8416
8417 /**
8418 * looks up the directive and returns true if it is a multi-element directive,
8419 * and therefore requires DOM nodes between -start and -end markers to be grouped
8420 * together.
8421 *
8422 * @param {string} name name of the directive to look up.
8423 * @returns true if directive was registered as multi-element.
8424 */
8425 function directiveIsMultiElement(name) {
8426 if (hasDirectives.hasOwnProperty(name)) {
8427 for (var directive, directives = $injector.get(name + Suffix),
8428 i = 0, ii = directives.length; i < ii; i++) {
8429 directive = directives[i];
8430 if (directive.multiElement) {
8431 return true;
8432 }
8433 }
8434 }
8435 return false;
8436 }
8437
8438 /**
8439 * When the element is replaced with HTML template then the new attributes
8440 * on the template need to be merged with the existing attributes in the DOM.
8441 * The desired effect is to have both of the attributes present.
8442 *
8443 * @param {object} dst destination attributes (original DOM)
8444 * @param {object} src source attributes (from the directive template)
8445 */
8446 function mergeTemplateAttributes(dst, src) {
8447 var srcAttr = src.$attr,
8448 dstAttr = dst.$attr,
8449 $element = dst.$$element;
8450
8451 // reapply the old attributes to the new element
8452 forEach(dst, function(value, key) {
8453 if (key.charAt(0) != '$') {
8454 if (src[key] && src[key] !== value) {
8455 value += (key === 'style' ? ';' : ' ') + src[key];
8456 }
8457 dst.$set(key, value, true, srcAttr[key]);
8458 }
8459 });
8460
8461 // copy the new attributes on the old attrs object
8462 forEach(src, function(value, key) {
8463 if (key == 'class') {
8464 safeAddClass($element, value);
8465 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8466 } else if (key == 'style') {
8467 $element.attr('style', $element.attr('style') + ';' + value);
8468 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8469 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8470 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8471 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8472 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8473 dst[key] = value;
8474 dstAttr[key] = srcAttr[key];
8475 }
8476 });
8477 }
8478
8479
8480 function compileTemplateUrl(directives, $compileNode, tAttrs,
8481 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8482 var linkQueue = [],
8483 afterTemplateNodeLinkFn,
8484 afterTemplateChildLinkFn,
8485 beforeTemplateCompileNode = $compileNode[0],
8486 origAsyncDirective = directives.shift(),
8487 derivedSyncDirective = inherit(origAsyncDirective, {
8488 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8489 }),
8490 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8491 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8492 : origAsyncDirective.templateUrl,
8493 templateNamespace = origAsyncDirective.templateNamespace;
8494
8495 $compileNode.empty();
8496
8497 $templateRequest(templateUrl)
8498 .then(function(content) {
8499 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8500
8501 content = denormalizeTemplate(content);
8502
8503 if (origAsyncDirective.replace) {
8504 if (jqLiteIsTextNode(content)) {
8505 $template = [];
8506 } else {
8507 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8508 }
8509 compileNode = $template[0];
8510
8511 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8512 throw $compileMinErr('tplrt',
8513 "Template for directive '{0}' must have exactly one root element. {1}",
8514 origAsyncDirective.name, templateUrl);
8515 }
8516
8517 tempTemplateAttrs = {$attr: {}};
8518 replaceWith($rootElement, $compileNode, compileNode);
8519 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8520
8521 if (isObject(origAsyncDirective.scope)) {
8522 // the original directive that caused the template to be loaded async required
8523 // an isolate scope
8524 markDirectiveScope(templateDirectives, true);
8525 }
8526 directives = templateDirectives.concat(directives);
8527 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8528 } else {
8529 compileNode = beforeTemplateCompileNode;
8530 $compileNode.html(content);
8531 }
8532
8533 directives.unshift(derivedSyncDirective);
8534
8535 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8536 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8537 previousCompileContext);
8538 forEach($rootElement, function(node, i) {
8539 if (node == compileNode) {
8540 $rootElement[i] = $compileNode[0];
8541 }
8542 });
8543 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8544
8545 while (linkQueue.length) {
8546 var scope = linkQueue.shift(),
8547 beforeTemplateLinkNode = linkQueue.shift(),
8548 linkRootElement = linkQueue.shift(),
8549 boundTranscludeFn = linkQueue.shift(),
8550 linkNode = $compileNode[0];
8551
8552 if (scope.$$destroyed) continue;
8553
8554 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8555 var oldClasses = beforeTemplateLinkNode.className;
8556
8557 if (!(previousCompileContext.hasElementTranscludeDirective &&
8558 origAsyncDirective.replace)) {
8559 // it was cloned therefore we have to clone as well.
8560 linkNode = jqLiteClone(compileNode);
8561 }
8562 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8563
8564 // Copy in CSS classes from original node
8565 safeAddClass(jqLite(linkNode), oldClasses);
8566 }
8567 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8568 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8569 } else {
8570 childBoundTranscludeFn = boundTranscludeFn;
8571 }
8572 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8573 childBoundTranscludeFn);
8574 }
8575 linkQueue = null;
8576 });
8577
8578 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8579 var childBoundTranscludeFn = boundTranscludeFn;
8580 if (scope.$$destroyed) return;
8581 if (linkQueue) {
8582 linkQueue.push(scope,
8583 node,
8584 rootElement,
8585 childBoundTranscludeFn);
8586 } else {
8587 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8588 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8589 }
8590 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8591 }
8592 };
8593 }
8594
8595
8596 /**
8597 * Sorting function for bound directives.
8598 */
8599 function byPriority(a, b) {
8600 var diff = b.priority - a.priority;
8601 if (diff !== 0) return diff;
8602 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8603 return a.index - b.index;
8604 }
8605
8606 function assertNoDuplicate(what, previousDirective, directive, element) {
8607
8608 function wrapModuleNameIfDefined(moduleName) {
8609 return moduleName ?
8610 (' (module: ' + moduleName + ')') :
8611 '';
8612 }
8613
8614 if (previousDirective) {
8615 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8616 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8617 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8618 }
8619 }
8620
8621
8622 function addTextInterpolateDirective(directives, text) {
8623 var interpolateFn = $interpolate(text, true);
8624 if (interpolateFn) {
8625 directives.push({
8626 priority: 0,
8627 compile: function textInterpolateCompileFn(templateNode) {
8628 var templateNodeParent = templateNode.parent(),
8629 hasCompileParent = !!templateNodeParent.length;
8630
8631 // When transcluding a template that has bindings in the root
8632 // we don't have a parent and thus need to add the class during linking fn.
8633 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8634
8635 return function textInterpolateLinkFn(scope, node) {
8636 var parent = node.parent();
8637 if (!hasCompileParent) compile.$$addBindingClass(parent);
8638 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8639 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8640 node[0].nodeValue = value;
8641 });
8642 };
8643 }
8644 });
8645 }
8646 }
8647
8648
8649 function wrapTemplate(type, template) {
8650 type = lowercase(type || 'html');
8651 switch (type) {
8652 case 'svg':
8653 case 'math':
8654 var wrapper = document.createElement('div');
8655 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8656 return wrapper.childNodes[0].childNodes;
8657 default:
8658 return template;
8659 }
8660 }
8661
8662
8663 function getTrustedContext(node, attrNormalizedName) {
8664 if (attrNormalizedName == "srcdoc") {
8665 return $sce.HTML;
8666 }
8667 var tag = nodeName_(node);
8668 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8669 if (attrNormalizedName == "xlinkHref" ||
8670 (tag == "form" && attrNormalizedName == "action") ||
8671 (tag != "img" && (attrNormalizedName == "src" ||
8672 attrNormalizedName == "ngSrc"))) {
8673 return $sce.RESOURCE_URL;
8674 }
8675 }
8676
8677
8678 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8679 var trustedContext = getTrustedContext(node, name);
8680 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8681
8682 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8683
8684 // no interpolation found -> ignore
8685 if (!interpolateFn) return;
8686
8687
8688 if (name === "multiple" && nodeName_(node) === "select") {
8689 throw $compileMinErr("selmulti",
8690 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8691 startingTag(node));
8692 }
8693
8694 directives.push({
8695 priority: 100,
8696 compile: function() {
8697 return {
8698 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8699 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8700
8701 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8702 throw $compileMinErr('nodomevents',
8703 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8704 "ng- versions (such as ng-click instead of onclick) instead.");
8705 }
8706
8707 // If the attribute has changed since last $interpolate()ed
8708 var newValue = attr[name];
8709 if (newValue !== value) {
8710 // we need to interpolate again since the attribute value has been updated
8711 // (e.g. by another directive's compile function)
8712 // ensure unset/empty values make interpolateFn falsy
8713 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8714 value = newValue;
8715 }
8716
8717 // if attribute was updated so that there is no interpolation going on we don't want to
8718 // register any observers
8719 if (!interpolateFn) return;
8720
8721 // initialize attr object so that it's ready in case we need the value for isolate
8722 // scope initialization, otherwise the value would not be available from isolate
8723 // directive's linking fn during linking phase
8724 attr[name] = interpolateFn(scope);
8725
8726 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8727 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8728 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8729 //special case for class attribute addition + removal
8730 //so that class changes can tap into the animation
8731 //hooks provided by the $animate service. Be sure to
8732 //skip animations when the first digest occurs (when
8733 //both the new and the old values are the same) since
8734 //the CSS classes are the non-interpolated values
8735 if (name === 'class' && newValue != oldValue) {
8736 attr.$updateClass(newValue, oldValue);
8737 } else {
8738 attr.$set(name, newValue);
8739 }
8740 });
8741 }
8742 };
8743 }
8744 });
8745 }
8746
8747
8748 /**
8749 * This is a special jqLite.replaceWith, which can replace items which
8750 * have no parents, provided that the containing jqLite collection is provided.
8751 *
8752 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8753 * in the root of the tree.
8754 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8755 * the shell, but replace its DOM node reference.
8756 * @param {Node} newNode The new DOM node.
8757 */
8758 function replaceWith($rootElement, elementsToRemove, newNode) {
8759 var firstElementToRemove = elementsToRemove[0],
8760 removeCount = elementsToRemove.length,
8761 parent = firstElementToRemove.parentNode,
8762 i, ii;
8763
8764 if ($rootElement) {
8765 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8766 if ($rootElement[i] == firstElementToRemove) {
8767 $rootElement[i++] = newNode;
8768 for (var j = i, j2 = j + removeCount - 1,
8769 jj = $rootElement.length;
8770 j < jj; j++, j2++) {
8771 if (j2 < jj) {
8772 $rootElement[j] = $rootElement[j2];
8773 } else {
8774 delete $rootElement[j];
8775 }
8776 }
8777 $rootElement.length -= removeCount - 1;
8778
8779 // If the replaced element is also the jQuery .context then replace it
8780 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8781 // http://api.jquery.com/context/
8782 if ($rootElement.context === firstElementToRemove) {
8783 $rootElement.context = newNode;
8784 }
8785 break;
8786 }
8787 }
8788 }
8789
8790 if (parent) {
8791 parent.replaceChild(newNode, firstElementToRemove);
8792 }
8793
8794 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8795 var fragment = document.createDocumentFragment();
8796 fragment.appendChild(firstElementToRemove);
8797
8798 if (jqLite.hasData(firstElementToRemove)) {
8799 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8800 // data here because there's no public interface in jQuery to do that and copying over
8801 // event listeners (which is the main use of private data) wouldn't work anyway.
8802 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8803
8804 // Remove data of the replaced element. We cannot just call .remove()
8805 // on the element it since that would deallocate scope that is needed
8806 // for the new node. Instead, remove the data "manually".
8807 if (!jQuery) {
8808 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8809 } else {
8810 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8811 // the replaced element. The cleanData version monkey-patched by Angular would cause
8812 // the scope to be trashed and we do need the very same scope to work with the new
8813 // element. However, we cannot just cache the non-patched version and use it here as
8814 // that would break if another library patches the method after Angular does (one
8815 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8816 // skipped this one time.
8817 skipDestroyOnNextJQueryCleanData = true;
8818 jQuery.cleanData([firstElementToRemove]);
8819 }
8820 }
8821
8822 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8823 var element = elementsToRemove[k];
8824 jqLite(element).remove(); // must do this way to clean up expando
8825 fragment.appendChild(element);
8826 delete elementsToRemove[k];
8827 }
8828
8829 elementsToRemove[0] = newNode;
8830 elementsToRemove.length = 1;
8831 }
8832
8833
8834 function cloneAndAnnotateFn(fn, annotation) {
8835 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8836 }
8837
8838
8839 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8840 try {
8841 linkFn(scope, $element, attrs, controllers, transcludeFn);
8842 } catch (e) {
8843 $exceptionHandler(e, startingTag($element));
8844 }
8845 }
8846
8847
8848 // Set up $watches for isolate scope and controller bindings. This process
8849 // only occurs for isolate scopes and new scopes with controllerAs.
8850 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8851 var removeWatchCollection = [];
8852 forEach(bindings, function(definition, scopeName) {
8853 var attrName = definition.attrName,
8854 optional = definition.optional,
8855 mode = definition.mode, // @, =, or &
8856 lastValue,
8857 parentGet, parentSet, compare;
8858
8859 switch (mode) {
8860
8861 case '@':
8862 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8863 destination[scopeName] = attrs[attrName] = void 0;
8864 }
8865 attrs.$observe(attrName, function(value) {
8866 if (isString(value)) {
8867 destination[scopeName] = value;
8868 }
8869 });
8870 attrs.$$observers[attrName].$$scope = scope;
8871 if (isString(attrs[attrName])) {
8872 // If the attribute has been provided then we trigger an interpolation to ensure
8873 // the value is there for use in the link fn
8874 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8875 }
8876 break;
8877
8878 case '=':
8879 if (!hasOwnProperty.call(attrs, attrName)) {
8880 if (optional) break;
8881 attrs[attrName] = void 0;
8882 }
8883 if (optional && !attrs[attrName]) break;
8884
8885 parentGet = $parse(attrs[attrName]);
8886 if (parentGet.literal) {
8887 compare = equals;
8888 } else {
8889 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8890 }
8891 parentSet = parentGet.assign || function() {
8892 // reset the change, or we will throw this exception on every $digest
8893 lastValue = destination[scopeName] = parentGet(scope);
8894 throw $compileMinErr('nonassign',
8895 "Expression '{0}' used with directive '{1}' is non-assignable!",
8896 attrs[attrName], directive.name);
8897 };
8898 lastValue = destination[scopeName] = parentGet(scope);
8899 var parentValueWatch = function parentValueWatch(parentValue) {
8900 if (!compare(parentValue, destination[scopeName])) {
8901 // we are out of sync and need to copy
8902 if (!compare(parentValue, lastValue)) {
8903 // parent changed and it has precedence
8904 destination[scopeName] = parentValue;
8905 } else {
8906 // if the parent can be assigned then do so
8907 parentSet(scope, parentValue = destination[scopeName]);
8908 }
8909 }
8910 return lastValue = parentValue;
8911 };
8912 parentValueWatch.$stateful = true;
8913 var removeWatch;
8914 if (definition.collection) {
8915 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8916 } else {
8917 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8918 }
8919 removeWatchCollection.push(removeWatch);
8920 break;
8921
8922 case '&':
8923 // Don't assign Object.prototype method to scope
8924 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
8925
8926 // Don't assign noop to destination if expression is not valid
8927 if (parentGet === noop && optional) break;
8928
8929 destination[scopeName] = function(locals) {
8930 return parentGet(scope, locals);
8931 };
8932 break;
8933 }
8934 });
8935
8936 return removeWatchCollection.length && function removeWatches() {
8937 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
8938 removeWatchCollection[i]();
8939 }
8940 };
8941 }
8942 }];
8943}
8944
8945var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
8946/**
8947 * Converts all accepted directives format into proper directive name.
8948 * @param name Name to normalize
8949 */
8950function directiveNormalize(name) {
8951 return camelCase(name.replace(PREFIX_REGEXP, ''));
8952}
8953
8954/**
8955 * @ngdoc type
8956 * @name $compile.directive.Attributes
8957 *
8958 * @description
8959 * A shared object between directive compile / linking functions which contains normalized DOM
8960 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
8961 * needed since all of these are treated as equivalent in Angular:
8962 *
8963 * ```
8964 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
8965 * ```
8966 */
8967
8968/**
8969 * @ngdoc property
8970 * @name $compile.directive.Attributes#$attr
8971 *
8972 * @description
8973 * A map of DOM element attribute names to the normalized name. This is
8974 * needed to do reverse lookup from normalized name back to actual name.
8975 */
8976
8977
8978/**
8979 * @ngdoc method
8980 * @name $compile.directive.Attributes#$set
8981 * @kind function
8982 *
8983 * @description
8984 * Set DOM element attribute value.
8985 *
8986 *
8987 * @param {string} name Normalized element attribute name of the property to modify. The name is
8988 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
8989 * property to the original name.
8990 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
8991 */
8992
8993
8994
8995/**
8996 * Closure compiler type information
8997 */
8998
8999function nodesetLinkingFn(
9000 /* angular.Scope */ scope,
9001 /* NodeList */ nodeList,
9002 /* Element */ rootElement,
9003 /* function(Function) */ boundTranscludeFn
9004) {}
9005
9006function directiveLinkingFn(
9007 /* nodesetLinkingFn */ nodesetLinkingFn,
9008 /* angular.Scope */ scope,
9009 /* Node */ node,
9010 /* Element */ rootElement,
9011 /* function(Function) */ boundTranscludeFn
9012) {}
9013
9014function tokenDifference(str1, str2) {
9015 var values = '',
9016 tokens1 = str1.split(/\s+/),
9017 tokens2 = str2.split(/\s+/);
9018
9019 outer:
9020 for (var i = 0; i < tokens1.length; i++) {
9021 var token = tokens1[i];
9022 for (var j = 0; j < tokens2.length; j++) {
9023 if (token == tokens2[j]) continue outer;
9024 }
9025 values += (values.length > 0 ? ' ' : '') + token;
9026 }
9027 return values;
9028}
9029
9030function removeComments(jqNodes) {
9031 jqNodes = jqLite(jqNodes);
9032 var i = jqNodes.length;
9033
9034 if (i <= 1) {
9035 return jqNodes;
9036 }
9037
9038 while (i--) {
9039 var node = jqNodes[i];
9040 if (node.nodeType === NODE_TYPE_COMMENT) {
9041 splice.call(jqNodes, i, 1);
9042 }
9043 }
9044 return jqNodes;
9045}
9046
9047var $controllerMinErr = minErr('$controller');
9048
9049
9050var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9051function identifierForController(controller, ident) {
9052 if (ident && isString(ident)) return ident;
9053 if (isString(controller)) {
9054 var match = CNTRL_REG.exec(controller);
9055 if (match) return match[3];
9056 }
9057}
9058
9059
9060/**
9061 * @ngdoc provider
9062 * @name $controllerProvider
9063 * @description
9064 * The {@link ng.$controller $controller service} is used by Angular to create new
9065 * controllers.
9066 *
9067 * This provider allows controller registration via the
9068 * {@link ng.$controllerProvider#register register} method.
9069 */
9070function $ControllerProvider() {
9071 var controllers = {},
9072 globals = false;
9073
9074 /**
9075 * @ngdoc method
9076 * @name $controllerProvider#register
9077 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9078 * the names and the values are the constructors.
9079 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9080 * annotations in the array notation).
9081 */
9082 this.register = function(name, constructor) {
9083 assertNotHasOwnProperty(name, 'controller');
9084 if (isObject(name)) {
9085 extend(controllers, name);
9086 } else {
9087 controllers[name] = constructor;
9088 }
9089 };
9090
9091 /**
9092 * @ngdoc method
9093 * @name $controllerProvider#allowGlobals
9094 * @description If called, allows `$controller` to find controller constructors on `window`
9095 */
9096 this.allowGlobals = function() {
9097 globals = true;
9098 };
9099
9100
9101 this.$get = ['$injector', '$window', function($injector, $window) {
9102
9103 /**
9104 * @ngdoc service
9105 * @name $controller
9106 * @requires $injector
9107 *
9108 * @param {Function|string} constructor If called with a function then it's considered to be the
9109 * controller constructor function. Otherwise it's considered to be a string which is used
9110 * to retrieve the controller constructor using the following steps:
9111 *
9112 * * check if a controller with given name is registered via `$controllerProvider`
9113 * * check if evaluating the string on the current scope returns a constructor
9114 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9115 * `window` object (not recommended)
9116 *
9117 * The string can use the `controller as property` syntax, where the controller instance is published
9118 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9119 * to work correctly.
9120 *
9121 * @param {Object} locals Injection locals for Controller.
9122 * @return {Object} Instance of given controller.
9123 *
9124 * @description
9125 * `$controller` service is responsible for instantiating controllers.
9126 *
9127 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9128 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9129 */
9130 return function(expression, locals, later, ident) {
9131 // PRIVATE API:
9132 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9133 // If true, $controller will allocate the object with the correct
9134 // prototype chain, but will not invoke the controller until a returned
9135 // callback is invoked.
9136 // param `ident` --- An optional label which overrides the label parsed from the controller
9137 // expression, if any.
9138 var instance, match, constructor, identifier;
9139 later = later === true;
9140 if (ident && isString(ident)) {
9141 identifier = ident;
9142 }
9143
9144 if (isString(expression)) {
9145 match = expression.match(CNTRL_REG);
9146 if (!match) {
9147 throw $controllerMinErr('ctrlfmt',
9148 "Badly formed controller string '{0}'. " +
9149 "Must match `__name__ as __id__` or `__name__`.", expression);
9150 }
9151 constructor = match[1],
9152 identifier = identifier || match[3];
9153 expression = controllers.hasOwnProperty(constructor)
9154 ? controllers[constructor]
9155 : getter(locals.$scope, constructor, true) ||
9156 (globals ? getter($window, constructor, true) : undefined);
9157
9158 assertArgFn(expression, constructor, true);
9159 }
9160
9161 if (later) {
9162 // Instantiate controller later:
9163 // This machinery is used to create an instance of the object before calling the
9164 // controller's constructor itself.
9165 //
9166 // This allows properties to be added to the controller before the constructor is
9167 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9168 //
9169 // This feature is not intended for use by applications, and is thus not documented
9170 // publicly.
9171 // Object creation: http://jsperf.com/create-constructor/2
9172 var controllerPrototype = (isArray(expression) ?
9173 expression[expression.length - 1] : expression).prototype;
9174 instance = Object.create(controllerPrototype || null);
9175
9176 if (identifier) {
9177 addIdentifier(locals, identifier, instance, constructor || expression.name);
9178 }
9179
9180 var instantiate;
9181 return instantiate = extend(function() {
9182 var result = $injector.invoke(expression, instance, locals, constructor);
9183 if (result !== instance && (isObject(result) || isFunction(result))) {
9184 instance = result;
9185 if (identifier) {
9186 // If result changed, re-assign controllerAs value to scope.
9187 addIdentifier(locals, identifier, instance, constructor || expression.name);
9188 }
9189 }
9190 return instance;
9191 }, {
9192 instance: instance,
9193 identifier: identifier
9194 });
9195 }
9196
9197 instance = $injector.instantiate(expression, locals, constructor);
9198
9199 if (identifier) {
9200 addIdentifier(locals, identifier, instance, constructor || expression.name);
9201 }
9202
9203 return instance;
9204 };
9205
9206 function addIdentifier(locals, identifier, instance, name) {
9207 if (!(locals && isObject(locals.$scope))) {
9208 throw minErr('$controller')('noscp',
9209 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9210 name, identifier);
9211 }
9212
9213 locals.$scope[identifier] = instance;
9214 }
9215 }];
9216}
9217
9218/**
9219 * @ngdoc service
9220 * @name $document
9221 * @requires $window
9222 *
9223 * @description
9224 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9225 *
9226 * @example
9227 <example module="documentExample">
9228 <file name="index.html">
9229 <div ng-controller="ExampleController">
9230 <p>$document title: <b ng-bind="title"></b></p>
9231 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9232 </div>
9233 </file>
9234 <file name="script.js">
9235 angular.module('documentExample', [])
9236 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9237 $scope.title = $document[0].title;
9238 $scope.windowTitle = angular.element(window.document)[0].title;
9239 }]);
9240 </file>
9241 </example>
9242 */
9243function $DocumentProvider() {
9244 this.$get = ['$window', function(window) {
9245 return jqLite(window.document);
9246 }];
9247}
9248
9249/**
9250 * @ngdoc service
9251 * @name $exceptionHandler
9252 * @requires ng.$log
9253 *
9254 * @description
9255 * Any uncaught exception in angular expressions is delegated to this service.
9256 * The default implementation simply delegates to `$log.error` which logs it into
9257 * the browser console.
9258 *
9259 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9260 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9261 *
9262 * ## Example:
9263 *
9264 * ```js
9265 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9266 * return function(exception, cause) {
9267 * exception.message += ' (caused by "' + cause + '")';
9268 * throw exception;
9269 * };
9270 * });
9271 * ```
9272 *
9273 * This example will override the normal action of `$exceptionHandler`, to make angular
9274 * exceptions fail hard when they happen, instead of just logging to the console.
9275 *
9276 * <hr />
9277 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9278 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9279 * (unless executed during a digest).
9280 *
9281 * If you wish, you can manually delegate exceptions, e.g.
9282 * `try { ... } catch(e) { $exceptionHandler(e); }`
9283 *
9284 * @param {Error} exception Exception associated with the error.
9285 * @param {string=} cause optional information about the context in which
9286 * the error was thrown.
9287 *
9288 */
9289function $ExceptionHandlerProvider() {
9290 this.$get = ['$log', function($log) {
9291 return function(exception, cause) {
9292 $log.error.apply($log, arguments);
9293 };
9294 }];
9295}
9296
9297var $$ForceReflowProvider = function() {
9298 this.$get = ['$document', function($document) {
9299 return function(domNode) {
9300 //the line below will force the browser to perform a repaint so
9301 //that all the animated elements within the animation frame will
9302 //be properly updated and drawn on screen. This is required to
9303 //ensure that the preparation animation is properly flushed so that
9304 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9305 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9306 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9307 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9308 if (domNode) {
9309 if (!domNode.nodeType && domNode instanceof jqLite) {
9310 domNode = domNode[0];
9311 }
9312 } else {
9313 domNode = $document[0].body;
9314 }
9315 return domNode.offsetWidth + 1;
9316 };
9317 }];
9318};
9319
9320var APPLICATION_JSON = 'application/json';
9321var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9322var JSON_START = /^\[|^\{(?!\{)/;
9323var JSON_ENDS = {
9324 '[': /]$/,
9325 '{': /}$/
9326};
9327var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9328var $httpMinErr = minErr('$http');
9329var $httpMinErrLegacyFn = function(method) {
9330 return function() {
9331 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9332 };
9333};
9334
9335function serializeValue(v) {
9336 if (isObject(v)) {
9337 return isDate(v) ? v.toISOString() : toJson(v);
9338 }
9339 return v;
9340}
9341
9342
9343function $HttpParamSerializerProvider() {
9344 /**
9345 * @ngdoc service
9346 * @name $httpParamSerializer
9347 * @description
9348 *
9349 * Default {@link $http `$http`} params serializer that converts objects to strings
9350 * according to the following rules:
9351 *
9352 * * `{'foo': 'bar'}` results in `foo=bar`
9353 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9354 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9355 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9356 *
9357 * Note that serializer will sort the request parameters alphabetically.
9358 * */
9359
9360 this.$get = function() {
9361 return function ngParamSerializer(params) {
9362 if (!params) return '';
9363 var parts = [];
9364 forEachSorted(params, function(value, key) {
9365 if (value === null || isUndefined(value)) return;
9366 if (isArray(value)) {
9367 forEach(value, function(v, k) {
9368 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9369 });
9370 } else {
9371 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9372 }
9373 });
9374
9375 return parts.join('&');
9376 };
9377 };
9378}
9379
9380function $HttpParamSerializerJQLikeProvider() {
9381 /**
9382 * @ngdoc service
9383 * @name $httpParamSerializerJQLike
9384 * @description
9385 *
9386 * Alternative {@link $http `$http`} params serializer that follows
9387 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9388 * The serializer will also sort the params alphabetically.
9389 *
9390 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9391 *
9392 * ```js
9393 * $http({
9394 * url: myUrl,
9395 * method: 'GET',
9396 * params: myParams,
9397 * paramSerializer: '$httpParamSerializerJQLike'
9398 * });
9399 * ```
9400 *
9401 * It is also possible to set it as the default `paramSerializer` in the
9402 * {@link $httpProvider#defaults `$httpProvider`}.
9403 *
9404 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9405 * form data for submission:
9406 *
9407 * ```js
9408 * .controller(function($http, $httpParamSerializerJQLike) {
9409 * //...
9410 *
9411 * $http({
9412 * url: myUrl,
9413 * method: 'POST',
9414 * data: $httpParamSerializerJQLike(myData),
9415 * headers: {
9416 * 'Content-Type': 'application/x-www-form-urlencoded'
9417 * }
9418 * });
9419 *
9420 * });
9421 * ```
9422 *
9423 * */
9424 this.$get = function() {
9425 return function jQueryLikeParamSerializer(params) {
9426 if (!params) return '';
9427 var parts = [];
9428 serialize(params, '', true);
9429 return parts.join('&');
9430
9431 function serialize(toSerialize, prefix, topLevel) {
9432 if (toSerialize === null || isUndefined(toSerialize)) return;
9433 if (isArray(toSerialize)) {
9434 forEach(toSerialize, function(value, index) {
9435 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9436 });
9437 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9438 forEachSorted(toSerialize, function(value, key) {
9439 serialize(value, prefix +
9440 (topLevel ? '' : '[') +
9441 key +
9442 (topLevel ? '' : ']'));
9443 });
9444 } else {
9445 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9446 }
9447 }
9448 };
9449 };
9450}
9451
9452function defaultHttpResponseTransform(data, headers) {
9453 if (isString(data)) {
9454 // Strip json vulnerability protection prefix and trim whitespace
9455 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9456
9457 if (tempData) {
9458 var contentType = headers('Content-Type');
9459 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9460 data = fromJson(tempData);
9461 }
9462 }
9463 }
9464
9465 return data;
9466}
9467
9468function isJsonLike(str) {
9469 var jsonStart = str.match(JSON_START);
9470 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9471}
9472
9473/**
9474 * Parse headers into key value object
9475 *
9476 * @param {string} headers Raw headers as a string
9477 * @returns {Object} Parsed headers as key value object
9478 */
9479function parseHeaders(headers) {
9480 var parsed = createMap(), i;
9481
9482 function fillInParsed(key, val) {
9483 if (key) {
9484 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9485 }
9486 }
9487
9488 if (isString(headers)) {
9489 forEach(headers.split('\n'), function(line) {
9490 i = line.indexOf(':');
9491 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9492 });
9493 } else if (isObject(headers)) {
9494 forEach(headers, function(headerVal, headerKey) {
9495 fillInParsed(lowercase(headerKey), trim(headerVal));
9496 });
9497 }
9498
9499 return parsed;
9500}
9501
9502
9503/**
9504 * Returns a function that provides access to parsed headers.
9505 *
9506 * Headers are lazy parsed when first requested.
9507 * @see parseHeaders
9508 *
9509 * @param {(string|Object)} headers Headers to provide access to.
9510 * @returns {function(string=)} Returns a getter function which if called with:
9511 *
9512 * - if called with single an argument returns a single header value or null
9513 * - if called with no arguments returns an object containing all headers.
9514 */
9515function headersGetter(headers) {
9516 var headersObj;
9517
9518 return function(name) {
9519 if (!headersObj) headersObj = parseHeaders(headers);
9520
9521 if (name) {
9522 var value = headersObj[lowercase(name)];
9523 if (value === void 0) {
9524 value = null;
9525 }
9526 return value;
9527 }
9528
9529 return headersObj;
9530 };
9531}
9532
9533
9534/**
9535 * Chain all given functions
9536 *
9537 * This function is used for both request and response transforming
9538 *
9539 * @param {*} data Data to transform.
9540 * @param {function(string=)} headers HTTP headers getter fn.
9541 * @param {number} status HTTP status code of the response.
9542 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9543 * @returns {*} Transformed data.
9544 */
9545function transformData(data, headers, status, fns) {
9546 if (isFunction(fns)) {
9547 return fns(data, headers, status);
9548 }
9549
9550 forEach(fns, function(fn) {
9551 data = fn(data, headers, status);
9552 });
9553
9554 return data;
9555}
9556
9557
9558function isSuccess(status) {
9559 return 200 <= status && status < 300;
9560}
9561
9562
9563/**
9564 * @ngdoc provider
9565 * @name $httpProvider
9566 * @description
9567 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9568 * */
9569function $HttpProvider() {
9570 /**
9571 * @ngdoc property
9572 * @name $httpProvider#defaults
9573 * @description
9574 *
9575 * Object containing default values for all {@link ng.$http $http} requests.
9576 *
9577 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9578 * that will provide the cache for all requests who set their `cache` property to `true`.
9579 * If you set the `defaults.cache = false` then only requests that specify their own custom
9580 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9581 *
9582 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9583 * Defaults value is `'XSRF-TOKEN'`.
9584 *
9585 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9586 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9587 *
9588 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9589 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9590 * setting default headers.
9591 * - **`defaults.headers.common`**
9592 * - **`defaults.headers.post`**
9593 * - **`defaults.headers.put`**
9594 * - **`defaults.headers.patch`**
9595 *
9596 *
9597 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9598 * used to the prepare string representation of request parameters (specified as an object).
9599 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9600 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9601 *
9602 **/
9603 var defaults = this.defaults = {
9604 // transform incoming response data
9605 transformResponse: [defaultHttpResponseTransform],
9606
9607 // transform outgoing request data
9608 transformRequest: [function(d) {
9609 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9610 }],
9611
9612 // default headers
9613 headers: {
9614 common: {
9615 'Accept': 'application/json, text/plain, */*'
9616 },
9617 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9618 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9619 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9620 },
9621
9622 xsrfCookieName: 'XSRF-TOKEN',
9623 xsrfHeaderName: 'X-XSRF-TOKEN',
9624
9625 paramSerializer: '$httpParamSerializer'
9626 };
9627
9628 var useApplyAsync = false;
9629 /**
9630 * @ngdoc method
9631 * @name $httpProvider#useApplyAsync
9632 * @description
9633 *
9634 * Configure $http service to combine processing of multiple http responses received at around
9635 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9636 * significant performance improvement for bigger applications that make many HTTP requests
9637 * concurrently (common during application bootstrap).
9638 *
9639 * Defaults to false. If no value is specified, returns the current configured value.
9640 *
9641 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9642 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9643 * to load and share the same digest cycle.
9644 *
9645 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9646 * otherwise, returns the current configured value.
9647 **/
9648 this.useApplyAsync = function(value) {
9649 if (isDefined(value)) {
9650 useApplyAsync = !!value;
9651 return this;
9652 }
9653 return useApplyAsync;
9654 };
9655
9656 var useLegacyPromise = true;
9657 /**
9658 * @ngdoc method
9659 * @name $httpProvider#useLegacyPromiseExtensions
9660 * @description
9661 *
9662 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9663 * This should be used to make sure that applications work without these methods.
9664 *
9665 * Defaults to true. If no value is specified, returns the current configured value.
9666 *
9667 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9668 *
9669 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9670 * otherwise, returns the current configured value.
9671 **/
9672 this.useLegacyPromiseExtensions = function(value) {
9673 if (isDefined(value)) {
9674 useLegacyPromise = !!value;
9675 return this;
9676 }
9677 return useLegacyPromise;
9678 };
9679
9680 /**
9681 * @ngdoc property
9682 * @name $httpProvider#interceptors
9683 * @description
9684 *
9685 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9686 * pre-processing of request or postprocessing of responses.
9687 *
9688 * These service factories are ordered by request, i.e. they are applied in the same order as the
9689 * array, on request, but reverse order, on response.
9690 *
9691 * {@link ng.$http#interceptors Interceptors detailed info}
9692 **/
9693 var interceptorFactories = this.interceptors = [];
9694
9695 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9696 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9697
9698 var defaultCache = $cacheFactory('$http');
9699
9700 /**
9701 * Make sure that default param serializer is exposed as a function
9702 */
9703 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9704 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9705
9706 /**
9707 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9708 * The reversal is needed so that we can build up the interception chain around the
9709 * server request.
9710 */
9711 var reversedInterceptors = [];
9712
9713 forEach(interceptorFactories, function(interceptorFactory) {
9714 reversedInterceptors.unshift(isString(interceptorFactory)
9715 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9716 });
9717
9718 /**
9719 * @ngdoc service
9720 * @kind function
9721 * @name $http
9722 * @requires ng.$httpBackend
9723 * @requires $cacheFactory
9724 * @requires $rootScope
9725 * @requires $q
9726 * @requires $injector
9727 *
9728 * @description
9729 * The `$http` service is a core Angular service that facilitates communication with the remote
9730 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9731 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9732 *
9733 * For unit testing applications that use `$http` service, see
9734 * {@link ngMock.$httpBackend $httpBackend mock}.
9735 *
9736 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9737 * $resource} service.
9738 *
9739 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9740 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9741 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9742 *
9743 *
9744 * ## General usage
9745 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9746 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9747 *
9748 * ```js
9749 * // Simple GET request example:
9750 * $http({
9751 * method: 'GET',
9752 * url: '/someUrl'
9753 * }).then(function successCallback(response) {
9754 * // this callback will be called asynchronously
9755 * // when the response is available
9756 * }, function errorCallback(response) {
9757 * // called asynchronously if an error occurs
9758 * // or server returns response with an error status.
9759 * });
9760 * ```
9761 *
9762 * The response object has these properties:
9763 *
9764 * - **data** – `{string|Object}` – The response body transformed with the transform
9765 * functions.
9766 * - **status** – `{number}` – HTTP status code of the response.
9767 * - **headers** – `{function([headerName])}` – Header getter function.
9768 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9769 * - **statusText** – `{string}` – HTTP status text of the response.
9770 *
9771 * A response status code between 200 and 299 is considered a success status and
9772 * will result in the success callback being called. Note that if the response is a redirect,
9773 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9774 * called for such responses.
9775 *
9776 *
9777 * ## Shortcut methods
9778 *
9779 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9780 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9781 * last argument.
9782 *
9783 * ```js
9784 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9785 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9786 * ```
9787 *
9788 * Complete list of shortcut methods:
9789 *
9790 * - {@link ng.$http#get $http.get}
9791 * - {@link ng.$http#head $http.head}
9792 * - {@link ng.$http#post $http.post}
9793 * - {@link ng.$http#put $http.put}
9794 * - {@link ng.$http#delete $http.delete}
9795 * - {@link ng.$http#jsonp $http.jsonp}
9796 * - {@link ng.$http#patch $http.patch}
9797 *
9798 *
9799 * ## Writing Unit Tests that use $http
9800 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9801 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9802 * request using trained responses.
9803 *
9804 * ```
9805 * $httpBackend.expectGET(...);
9806 * $http.get(...);
9807 * $httpBackend.flush();
9808 * ```
9809 *
9810 * ## Deprecation Notice
9811 * <div class="alert alert-danger">
9812 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9813 * Use the standard `then` method instead.
9814 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9815 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9816 * </div>
9817 *
9818 * ## Setting HTTP Headers
9819 *
9820 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9821 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9822 * object, which currently contains this default configuration:
9823 *
9824 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9825 * - `Accept: application/json, text/plain, * / *`
9826 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9827 * - `Content-Type: application/json`
9828 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9829 * - `Content-Type: application/json`
9830 *
9831 * To add or overwrite these defaults, simply add or remove a property from these configuration
9832 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9833 * with the lowercased HTTP method name as the key, e.g.
9834 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9835 *
9836 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9837 * fashion. For example:
9838 *
9839 * ```
9840 * module.run(function($http) {
9841 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9842 * });
9843 * ```
9844 *
9845 * In addition, you can supply a `headers` property in the config object passed when
9846 * calling `$http(config)`, which overrides the defaults without changing them globally.
9847 *
9848 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9849 * Use the `headers` property, setting the desired header to `undefined`. For example:
9850 *
9851 * ```js
9852 * var req = {
9853 * method: 'POST',
9854 * url: 'http://example.com',
9855 * headers: {
9856 * 'Content-Type': undefined
9857 * },
9858 * data: { test: 'test' }
9859 * }
9860 *
9861 * $http(req).then(function(){...}, function(){...});
9862 * ```
9863 *
9864 * ## Transforming Requests and Responses
9865 *
9866 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9867 * and `transformResponse`. These properties can be a single function that returns
9868 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9869 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9870 *
9871 * ### Default Transformations
9872 *
9873 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9874 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9875 * then these will be applied.
9876 *
9877 * You can augment or replace the default transformations by modifying these properties by adding to or
9878 * replacing the array.
9879 *
9880 * Angular provides the following default transformations:
9881 *
9882 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9883 *
9884 * - If the `data` property of the request configuration object contains an object, serialize it
9885 * into JSON format.
9886 *
9887 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9888 *
9889 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9890 * - If JSON response is detected, deserialize it using a JSON parser.
9891 *
9892 *
9893 * ### Overriding the Default Transformations Per Request
9894 *
9895 * If you wish override the request/response transformations only for a single request then provide
9896 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9897 * into `$http`.
9898 *
9899 * Note that if you provide these properties on the config object the default transformations will be
9900 * overwritten. If you wish to augment the default transformations then you must include them in your
9901 * local transformation array.
9902 *
9903 * The following code demonstrates adding a new response transformation to be run after the default response
9904 * transformations have been run.
9905 *
9906 * ```js
9907 * function appendTransform(defaults, transform) {
9908 *
9909 * // We can't guarantee that the default transformation is an array
9910 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9911 *
9912 * // Append the new transformation to the defaults
9913 * return defaults.concat(transform);
9914 * }
9915 *
9916 * $http({
9917 * url: '...',
9918 * method: 'GET',
9919 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9920 * return doTransform(value);
9921 * })
9922 * });
9923 * ```
9924 *
9925 *
9926 * ## Caching
9927 *
9928 * To enable caching, set the request configuration `cache` property to `true` (to use default
9929 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
9930 * When the cache is enabled, `$http` stores the response from the server in the specified
9931 * cache. The next time the same request is made, the response is served from the cache without
9932 * sending a request to the server.
9933 *
9934 * Note that even if the response is served from cache, delivery of the data is asynchronous in
9935 * the same way that real requests are.
9936 *
9937 * If there are multiple GET requests for the same URL that should be cached using the same
9938 * cache, but the cache is not populated yet, only one request to the server will be made and
9939 * the remaining requests will be fulfilled using the response from the first request.
9940 *
9941 * You can change the default cache to a new object (built with
9942 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
9943 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
9944 * their `cache` property to `true` will now use this cache object.
9945 *
9946 * If you set the default cache to `false` then only requests that specify their own custom
9947 * cache object will be cached.
9948 *
9949 * ## Interceptors
9950 *
9951 * Before you start creating interceptors, be sure to understand the
9952 * {@link ng.$q $q and deferred/promise APIs}.
9953 *
9954 * For purposes of global error handling, authentication, or any kind of synchronous or
9955 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
9956 * able to intercept requests before they are handed to the server and
9957 * responses before they are handed over to the application code that
9958 * initiated these requests. The interceptors leverage the {@link ng.$q
9959 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
9960 *
9961 * The interceptors are service factories that are registered with the `$httpProvider` by
9962 * adding them to the `$httpProvider.interceptors` array. The factory is called and
9963 * injected with dependencies (if specified) and returns the interceptor.
9964 *
9965 * There are two kinds of interceptors (and two kinds of rejection interceptors):
9966 *
9967 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
9968 * modify the `config` object or create a new one. The function needs to return the `config`
9969 * object directly, or a promise containing the `config` or a new `config` object.
9970 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
9971 * resolved with a rejection.
9972 * * `response`: interceptors get called with http `response` object. The function is free to
9973 * modify the `response` object or create a new one. The function needs to return the `response`
9974 * object directly, or as a promise containing the `response` or a new `response` object.
9975 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
9976 * resolved with a rejection.
9977 *
9978 *
9979 * ```js
9980 * // register the interceptor as a service
9981 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
9982 * return {
9983 * // optional method
9984 * 'request': function(config) {
9985 * // do something on success
9986 * return config;
9987 * },
9988 *
9989 * // optional method
9990 * 'requestError': function(rejection) {
9991 * // do something on error
9992 * if (canRecover(rejection)) {
9993 * return responseOrNewPromise
9994 * }
9995 * return $q.reject(rejection);
9996 * },
9997 *
9998 *
9999 *
10000 * // optional method
10001 * 'response': function(response) {
10002 * // do something on success
10003 * return response;
10004 * },
10005 *
10006 * // optional method
10007 * 'responseError': function(rejection) {
10008 * // do something on error
10009 * if (canRecover(rejection)) {
10010 * return responseOrNewPromise
10011 * }
10012 * return $q.reject(rejection);
10013 * }
10014 * };
10015 * });
10016 *
10017 * $httpProvider.interceptors.push('myHttpInterceptor');
10018 *
10019 *
10020 * // alternatively, register the interceptor via an anonymous factory
10021 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10022 * return {
10023 * 'request': function(config) {
10024 * // same as above
10025 * },
10026 *
10027 * 'response': function(response) {
10028 * // same as above
10029 * }
10030 * };
10031 * });
10032 * ```
10033 *
10034 * ## Security Considerations
10035 *
10036 * When designing web applications, consider security threats from:
10037 *
10038 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10039 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10040 *
10041 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10042 * pre-configured with strategies that address these issues, but for this to work backend server
10043 * cooperation is required.
10044 *
10045 * ### JSON Vulnerability Protection
10046 *
10047 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10048 * allows third party website to turn your JSON resource URL into
10049 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10050 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10051 * Angular will automatically strip the prefix before processing it as JSON.
10052 *
10053 * For example if your server needs to return:
10054 * ```js
10055 * ['one','two']
10056 * ```
10057 *
10058 * which is vulnerable to attack, your server can return:
10059 * ```js
10060 * )]}',
10061 * ['one','two']
10062 * ```
10063 *
10064 * Angular will strip the prefix, before processing the JSON.
10065 *
10066 *
10067 * ### Cross Site Request Forgery (XSRF) Protection
10068 *
10069 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10070 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10071 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10072 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10073 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10074 * the XHR came from JavaScript running on your domain. The header will not be set for
10075 * cross-domain requests.
10076 *
10077 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10078 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10079 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10080 * that only JavaScript running on your domain could have sent the request. The token must be
10081 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10082 * making up its own tokens). We recommend that the token is a digest of your site's
10083 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
10084 * for added security.
10085 *
10086 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10087 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10088 * or the per-request config object.
10089 *
10090 * In order to prevent collisions in environments where multiple Angular apps share the
10091 * same domain or subdomain, we recommend that each application uses unique cookie name.
10092 *
10093 * @param {object} config Object describing the request to be made and how it should be
10094 * processed. The object has following properties:
10095 *
10096 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10097 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10098 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10099 * with the `paramSerializer` and appended as GET parameters.
10100 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10101 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10102 * HTTP headers to send to the server. If the return value of a function is null, the
10103 * header will not be sent. Functions accept a config object as an argument.
10104 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10105 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10106 * - **transformRequest** –
10107 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10108 * transform function or an array of such functions. The transform function takes the http
10109 * request body and headers and returns its transformed (typically serialized) version.
10110 * See {@link ng.$http#overriding-the-default-transformations-per-request
10111 * Overriding the Default Transformations}
10112 * - **transformResponse** –
10113 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10114 * transform function or an array of such functions. The transform function takes the http
10115 * response body, headers and status and returns its transformed (typically deserialized) version.
10116 * See {@link ng.$http#overriding-the-default-transformations-per-request
10117 * Overriding the Default TransformationjqLiks}
10118 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10119 * prepare the string representation of request parameters (specified as an object).
10120 * If specified as string, it is interpreted as function registered with the
10121 * {@link $injector $injector}, which means you can create your own serializer
10122 * by registering it as a {@link auto.$provide#service service}.
10123 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10124 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10125 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10126 * GET request, otherwise if a cache instance built with
10127 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10128 * caching.
10129 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10130 * that should abort the request when resolved.
10131 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10132 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10133 * for more information.
10134 * - **responseType** - `{string}` - see
10135 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10136 *
10137 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10138 * when the request succeeds or fails.
10139 *
10140 *
10141 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10142 * requests. This is primarily meant to be used for debugging purposes.
10143 *
10144 *
10145 * @example
10146<example module="httpExample">
10147<file name="index.html">
10148 <div ng-controller="FetchController">
10149 <select ng-model="method" aria-label="Request method">
10150 <option>GET</option>
10151 <option>JSONP</option>
10152 </select>
10153 <input type="text" ng-model="url" size="80" aria-label="URL" />
10154 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10155 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10156 <button id="samplejsonpbtn"
10157 ng-click="updateModel('JSONP',
10158 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10159 Sample JSONP
10160 </button>
10161 <button id="invalidjsonpbtn"
10162 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10163 Invalid JSONP
10164 </button>
10165 <pre>http status code: {{status}}</pre>
10166 <pre>http response data: {{data}}</pre>
10167 </div>
10168</file>
10169<file name="script.js">
10170 angular.module('httpExample', [])
10171 .controller('FetchController', ['$scope', '$http', '$templateCache',
10172 function($scope, $http, $templateCache) {
10173 $scope.method = 'GET';
10174 $scope.url = 'http-hello.html';
10175
10176 $scope.fetch = function() {
10177 $scope.code = null;
10178 $scope.response = null;
10179
10180 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10181 then(function(response) {
10182 $scope.status = response.status;
10183 $scope.data = response.data;
10184 }, function(response) {
10185 $scope.data = response.data || "Request failed";
10186 $scope.status = response.status;
10187 });
10188 };
10189
10190 $scope.updateModel = function(method, url) {
10191 $scope.method = method;
10192 $scope.url = url;
10193 };
10194 }]);
10195</file>
10196<file name="http-hello.html">
10197 Hello, $http!
10198</file>
10199<file name="protractor.js" type="protractor">
10200 var status = element(by.binding('status'));
10201 var data = element(by.binding('data'));
10202 var fetchBtn = element(by.id('fetchbtn'));
10203 var sampleGetBtn = element(by.id('samplegetbtn'));
10204 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10205 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10206
10207 it('should make an xhr GET request', function() {
10208 sampleGetBtn.click();
10209 fetchBtn.click();
10210 expect(status.getText()).toMatch('200');
10211 expect(data.getText()).toMatch(/Hello, \$http!/);
10212 });
10213
10214// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10215// it('should make a JSONP request to angularjs.org', function() {
10216// sampleJsonpBtn.click();
10217// fetchBtn.click();
10218// expect(status.getText()).toMatch('200');
10219// expect(data.getText()).toMatch(/Super Hero!/);
10220// });
10221
10222 it('should make JSONP request to invalid URL and invoke the error handler',
10223 function() {
10224 invalidJsonpBtn.click();
10225 fetchBtn.click();
10226 expect(status.getText()).toMatch('0');
10227 expect(data.getText()).toMatch('Request failed');
10228 });
10229</file>
10230</example>
10231 */
10232 function $http(requestConfig) {
10233
10234 if (!angular.isObject(requestConfig)) {
10235 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10236 }
10237
10238 var config = extend({
10239 method: 'get',
10240 transformRequest: defaults.transformRequest,
10241 transformResponse: defaults.transformResponse,
10242 paramSerializer: defaults.paramSerializer
10243 }, requestConfig);
10244
10245 config.headers = mergeHeaders(requestConfig);
10246 config.method = uppercase(config.method);
10247 config.paramSerializer = isString(config.paramSerializer) ?
10248 $injector.get(config.paramSerializer) : config.paramSerializer;
10249
10250 var serverRequest = function(config) {
10251 var headers = config.headers;
10252 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10253
10254 // strip content-type if data is undefined
10255 if (isUndefined(reqData)) {
10256 forEach(headers, function(value, header) {
10257 if (lowercase(header) === 'content-type') {
10258 delete headers[header];
10259 }
10260 });
10261 }
10262
10263 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10264 config.withCredentials = defaults.withCredentials;
10265 }
10266
10267 // send request
10268 return sendReq(config, reqData).then(transformResponse, transformResponse);
10269 };
10270
10271 var chain = [serverRequest, undefined];
10272 var promise = $q.when(config);
10273
10274 // apply interceptors
10275 forEach(reversedInterceptors, function(interceptor) {
10276 if (interceptor.request || interceptor.requestError) {
10277 chain.unshift(interceptor.request, interceptor.requestError);
10278 }
10279 if (interceptor.response || interceptor.responseError) {
10280 chain.push(interceptor.response, interceptor.responseError);
10281 }
10282 });
10283
10284 while (chain.length) {
10285 var thenFn = chain.shift();
10286 var rejectFn = chain.shift();
10287
10288 promise = promise.then(thenFn, rejectFn);
10289 }
10290
10291 if (useLegacyPromise) {
10292 promise.success = function(fn) {
10293 assertArgFn(fn, 'fn');
10294
10295 promise.then(function(response) {
10296 fn(response.data, response.status, response.headers, config);
10297 });
10298 return promise;
10299 };
10300
10301 promise.error = function(fn) {
10302 assertArgFn(fn, 'fn');
10303
10304 promise.then(null, function(response) {
10305 fn(response.data, response.status, response.headers, config);
10306 });
10307 return promise;
10308 };
10309 } else {
10310 promise.success = $httpMinErrLegacyFn('success');
10311 promise.error = $httpMinErrLegacyFn('error');
10312 }
10313
10314 return promise;
10315
10316 function transformResponse(response) {
10317 // make a copy since the response must be cacheable
10318 var resp = extend({}, response);
10319 resp.data = transformData(response.data, response.headers, response.status,
10320 config.transformResponse);
10321 return (isSuccess(response.status))
10322 ? resp
10323 : $q.reject(resp);
10324 }
10325
10326 function executeHeaderFns(headers, config) {
10327 var headerContent, processedHeaders = {};
10328
10329 forEach(headers, function(headerFn, header) {
10330 if (isFunction(headerFn)) {
10331 headerContent = headerFn(config);
10332 if (headerContent != null) {
10333 processedHeaders[header] = headerContent;
10334 }
10335 } else {
10336 processedHeaders[header] = headerFn;
10337 }
10338 });
10339
10340 return processedHeaders;
10341 }
10342
10343 function mergeHeaders(config) {
10344 var defHeaders = defaults.headers,
10345 reqHeaders = extend({}, config.headers),
10346 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10347
10348 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10349
10350 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10351 defaultHeadersIteration:
10352 for (defHeaderName in defHeaders) {
10353 lowercaseDefHeaderName = lowercase(defHeaderName);
10354
10355 for (reqHeaderName in reqHeaders) {
10356 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10357 continue defaultHeadersIteration;
10358 }
10359 }
10360
10361 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10362 }
10363
10364 // execute if header value is a function for merged headers
10365 return executeHeaderFns(reqHeaders, shallowCopy(config));
10366 }
10367 }
10368
10369 $http.pendingRequests = [];
10370
10371 /**
10372 * @ngdoc method
10373 * @name $http#get
10374 *
10375 * @description
10376 * Shortcut method to perform `GET` request.
10377 *
10378 * @param {string} url Relative or absolute URL specifying the destination of the request
10379 * @param {Object=} config Optional configuration object
10380 * @returns {HttpPromise} Future object
10381 */
10382
10383 /**
10384 * @ngdoc method
10385 * @name $http#delete
10386 *
10387 * @description
10388 * Shortcut method to perform `DELETE` request.
10389 *
10390 * @param {string} url Relative or absolute URL specifying the destination of the request
10391 * @param {Object=} config Optional configuration object
10392 * @returns {HttpPromise} Future object
10393 */
10394
10395 /**
10396 * @ngdoc method
10397 * @name $http#head
10398 *
10399 * @description
10400 * Shortcut method to perform `HEAD` request.
10401 *
10402 * @param {string} url Relative or absolute URL specifying the destination of the request
10403 * @param {Object=} config Optional configuration object
10404 * @returns {HttpPromise} Future object
10405 */
10406
10407 /**
10408 * @ngdoc method
10409 * @name $http#jsonp
10410 *
10411 * @description
10412 * Shortcut method to perform `JSONP` request.
10413 *
10414 * @param {string} url Relative or absolute URL specifying the destination of the request.
10415 * The name of the callback should be the string `JSON_CALLBACK`.
10416 * @param {Object=} config Optional configuration object
10417 * @returns {HttpPromise} Future object
10418 */
10419 createShortMethods('get', 'delete', 'head', 'jsonp');
10420
10421 /**
10422 * @ngdoc method
10423 * @name $http#post
10424 *
10425 * @description
10426 * Shortcut method to perform `POST` request.
10427 *
10428 * @param {string} url Relative or absolute URL specifying the destination of the request
10429 * @param {*} data Request content
10430 * @param {Object=} config Optional configuration object
10431 * @returns {HttpPromise} Future object
10432 */
10433
10434 /**
10435 * @ngdoc method
10436 * @name $http#put
10437 *
10438 * @description
10439 * Shortcut method to perform `PUT` request.
10440 *
10441 * @param {string} url Relative or absolute URL specifying the destination of the request
10442 * @param {*} data Request content
10443 * @param {Object=} config Optional configuration object
10444 * @returns {HttpPromise} Future object
10445 */
10446
10447 /**
10448 * @ngdoc method
10449 * @name $http#patch
10450 *
10451 * @description
10452 * Shortcut method to perform `PATCH` request.
10453 *
10454 * @param {string} url Relative or absolute URL specifying the destination of the request
10455 * @param {*} data Request content
10456 * @param {Object=} config Optional configuration object
10457 * @returns {HttpPromise} Future object
10458 */
10459 createShortMethodsWithData('post', 'put', 'patch');
10460
10461 /**
10462 * @ngdoc property
10463 * @name $http#defaults
10464 *
10465 * @description
10466 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10467 * default headers, withCredentials as well as request and response transformations.
10468 *
10469 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10470 */
10471 $http.defaults = defaults;
10472
10473
10474 return $http;
10475
10476
10477 function createShortMethods(names) {
10478 forEach(arguments, function(name) {
10479 $http[name] = function(url, config) {
10480 return $http(extend({}, config || {}, {
10481 method: name,
10482 url: url
10483 }));
10484 };
10485 });
10486 }
10487
10488
10489 function createShortMethodsWithData(name) {
10490 forEach(arguments, function(name) {
10491 $http[name] = function(url, data, config) {
10492 return $http(extend({}, config || {}, {
10493 method: name,
10494 url: url,
10495 data: data
10496 }));
10497 };
10498 });
10499 }
10500
10501
10502 /**
10503 * Makes the request.
10504 *
10505 * !!! ACCESSES CLOSURE VARS:
10506 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10507 */
10508 function sendReq(config, reqData) {
10509 var deferred = $q.defer(),
10510 promise = deferred.promise,
10511 cache,
10512 cachedResp,
10513 reqHeaders = config.headers,
10514 url = buildUrl(config.url, config.paramSerializer(config.params));
10515
10516 $http.pendingRequests.push(config);
10517 promise.then(removePendingReq, removePendingReq);
10518
10519
10520 if ((config.cache || defaults.cache) && config.cache !== false &&
10521 (config.method === 'GET' || config.method === 'JSONP')) {
10522 cache = isObject(config.cache) ? config.cache
10523 : isObject(defaults.cache) ? defaults.cache
10524 : defaultCache;
10525 }
10526
10527 if (cache) {
10528 cachedResp = cache.get(url);
10529 if (isDefined(cachedResp)) {
10530 if (isPromiseLike(cachedResp)) {
10531 // cached request has already been sent, but there is no response yet
10532 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10533 } else {
10534 // serving from cache
10535 if (isArray(cachedResp)) {
10536 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10537 } else {
10538 resolvePromise(cachedResp, 200, {}, 'OK');
10539 }
10540 }
10541 } else {
10542 // put the promise for the non-transformed response into cache as a placeholder
10543 cache.put(url, promise);
10544 }
10545 }
10546
10547
10548 // if we won't have the response in cache, set the xsrf headers and
10549 // send the request to the backend
10550 if (isUndefined(cachedResp)) {
10551 var xsrfValue = urlIsSameOrigin(config.url)
10552 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10553 : undefined;
10554 if (xsrfValue) {
10555 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10556 }
10557
10558 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10559 config.withCredentials, config.responseType);
10560 }
10561
10562 return promise;
10563
10564
10565 /**
10566 * Callback registered to $httpBackend():
10567 * - caches the response if desired
10568 * - resolves the raw $http promise
10569 * - calls $apply
10570 */
10571 function done(status, response, headersString, statusText) {
10572 if (cache) {
10573 if (isSuccess(status)) {
10574 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10575 } else {
10576 // remove promise from the cache
10577 cache.remove(url);
10578 }
10579 }
10580
10581 function resolveHttpPromise() {
10582 resolvePromise(response, status, headersString, statusText);
10583 }
10584
10585 if (useApplyAsync) {
10586 $rootScope.$applyAsync(resolveHttpPromise);
10587 } else {
10588 resolveHttpPromise();
10589 if (!$rootScope.$$phase) $rootScope.$apply();
10590 }
10591 }
10592
10593
10594 /**
10595 * Resolves the raw $http promise.
10596 */
10597 function resolvePromise(response, status, headers, statusText) {
10598 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10599 status = status >= -1 ? status : 0;
10600
10601 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10602 data: response,
10603 status: status,
10604 headers: headersGetter(headers),
10605 config: config,
10606 statusText: statusText
10607 });
10608 }
10609
10610 function resolvePromiseWithResult(result) {
10611 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10612 }
10613
10614 function removePendingReq() {
10615 var idx = $http.pendingRequests.indexOf(config);
10616 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10617 }
10618 }
10619
10620
10621 function buildUrl(url, serializedParams) {
10622 if (serializedParams.length > 0) {
10623 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10624 }
10625 return url;
10626 }
10627 }];
10628}
10629
10630/**
10631 * @ngdoc service
10632 * @name $xhrFactory
10633 *
10634 * @description
10635 * Factory function used to create XMLHttpRequest objects.
10636 *
10637 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10638 *
10639 * ```
10640 * angular.module('myApp', [])
10641 * .factory('$xhrFactory', function() {
10642 * return function createXhr(method, url) {
10643 * return new window.XMLHttpRequest({mozSystem: true});
10644 * };
10645 * });
10646 * ```
10647 *
10648 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10649 * @param {string} url URL of the request.
10650 */
10651function $xhrFactoryProvider() {
10652 this.$get = function() {
10653 return function createXhr() {
10654 return new window.XMLHttpRequest();
10655 };
10656 };
10657}
10658
10659/**
10660 * @ngdoc service
10661 * @name $httpBackend
10662 * @requires $window
10663 * @requires $document
10664 * @requires $xhrFactory
10665 *
10666 * @description
10667 * HTTP backend used by the {@link ng.$http service} that delegates to
10668 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10669 *
10670 * You should never need to use this service directly, instead use the higher-level abstractions:
10671 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10672 *
10673 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10674 * $httpBackend} which can be trained with responses.
10675 */
10676function $HttpBackendProvider() {
10677 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10678 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10679 }];
10680}
10681
10682function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10683 // TODO(vojta): fix the signature
10684 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10685 $browser.$$incOutstandingRequestCount();
10686 url = url || $browser.url();
10687
10688 if (lowercase(method) == 'jsonp') {
10689 var callbackId = '_' + (callbacks.counter++).toString(36);
10690 callbacks[callbackId] = function(data) {
10691 callbacks[callbackId].data = data;
10692 callbacks[callbackId].called = true;
10693 };
10694
10695 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10696 callbackId, function(status, text) {
10697 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10698 callbacks[callbackId] = noop;
10699 });
10700 } else {
10701
10702 var xhr = createXhr(method, url);
10703
10704 xhr.open(method, url, true);
10705 forEach(headers, function(value, key) {
10706 if (isDefined(value)) {
10707 xhr.setRequestHeader(key, value);
10708 }
10709 });
10710
10711 xhr.onload = function requestLoaded() {
10712 var statusText = xhr.statusText || '';
10713
10714 // responseText is the old-school way of retrieving response (supported by IE9)
10715 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10716 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10717
10718 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10719 var status = xhr.status === 1223 ? 204 : xhr.status;
10720
10721 // fix status code when it is 0 (0 status is undocumented).
10722 // Occurs when accessing file resources or on Android 4.1 stock browser
10723 // while retrieving files from application cache.
10724 if (status === 0) {
10725 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10726 }
10727
10728 completeRequest(callback,
10729 status,
10730 response,
10731 xhr.getAllResponseHeaders(),
10732 statusText);
10733 };
10734
10735 var requestError = function() {
10736 // The response is always empty
10737 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10738 completeRequest(callback, -1, null, null, '');
10739 };
10740
10741 xhr.onerror = requestError;
10742 xhr.onabort = requestError;
10743
10744 if (withCredentials) {
10745 xhr.withCredentials = true;
10746 }
10747
10748 if (responseType) {
10749 try {
10750 xhr.responseType = responseType;
10751 } catch (e) {
10752 // WebKit added support for the json responseType value on 09/03/2013
10753 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10754 // known to throw when setting the value "json" as the response type. Other older
10755 // browsers implementing the responseType
10756 //
10757 // The json response type can be ignored if not supported, because JSON payloads are
10758 // parsed on the client-side regardless.
10759 if (responseType !== 'json') {
10760 throw e;
10761 }
10762 }
10763 }
10764
10765 xhr.send(isUndefined(post) ? null : post);
10766 }
10767
10768 if (timeout > 0) {
10769 var timeoutId = $browserDefer(timeoutRequest, timeout);
10770 } else if (isPromiseLike(timeout)) {
10771 timeout.then(timeoutRequest);
10772 }
10773
10774
10775 function timeoutRequest() {
10776 jsonpDone && jsonpDone();
10777 xhr && xhr.abort();
10778 }
10779
10780 function completeRequest(callback, status, response, headersString, statusText) {
10781 // cancel timeout and subsequent timeout promise resolution
10782 if (isDefined(timeoutId)) {
10783 $browserDefer.cancel(timeoutId);
10784 }
10785 jsonpDone = xhr = null;
10786
10787 callback(status, response, headersString, statusText);
10788 $browser.$$completeOutstandingRequest(noop);
10789 }
10790 };
10791
10792 function jsonpReq(url, callbackId, done) {
10793 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10794 // - fetches local scripts via XHR and evals them
10795 // - adds and immediately removes script elements from the document
10796 var script = rawDocument.createElement('script'), callback = null;
10797 script.type = "text/javascript";
10798 script.src = url;
10799 script.async = true;
10800
10801 callback = function(event) {
10802 removeEventListenerFn(script, "load", callback);
10803 removeEventListenerFn(script, "error", callback);
10804 rawDocument.body.removeChild(script);
10805 script = null;
10806 var status = -1;
10807 var text = "unknown";
10808
10809 if (event) {
10810 if (event.type === "load" && !callbacks[callbackId].called) {
10811 event = { type: "error" };
10812 }
10813 text = event.type;
10814 status = event.type === "error" ? 404 : 200;
10815 }
10816
10817 if (done) {
10818 done(status, text);
10819 }
10820 };
10821
10822 addEventListenerFn(script, "load", callback);
10823 addEventListenerFn(script, "error", callback);
10824 rawDocument.body.appendChild(script);
10825 return callback;
10826 }
10827}
10828
10829var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10830$interpolateMinErr.throwNoconcat = function(text) {
10831 throw $interpolateMinErr('noconcat',
10832 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10833 "interpolations that concatenate multiple expressions when a trusted value is " +
10834 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10835};
10836
10837$interpolateMinErr.interr = function(text, err) {
10838 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10839};
10840
10841/**
10842 * @ngdoc provider
10843 * @name $interpolateProvider
10844 *
10845 * @description
10846 *
10847 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10848 *
10849 * @example
10850<example module="customInterpolationApp">
10851<file name="index.html">
10852<script>
10853 var customInterpolationApp = angular.module('customInterpolationApp', []);
10854
10855 customInterpolationApp.config(function($interpolateProvider) {
10856 $interpolateProvider.startSymbol('//');
10857 $interpolateProvider.endSymbol('//');
10858 });
10859
10860
10861 customInterpolationApp.controller('DemoController', function() {
10862 this.label = "This binding is brought you by // interpolation symbols.";
10863 });
10864</script>
10865<div ng-app="App" ng-controller="DemoController as demo">
10866 //demo.label//
10867</div>
10868</file>
10869<file name="protractor.js" type="protractor">
10870 it('should interpolate binding with custom symbols', function() {
10871 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10872 });
10873</file>
10874</example>
10875 */
10876function $InterpolateProvider() {
10877 var startSymbol = '{{';
10878 var endSymbol = '}}';
10879
10880 /**
10881 * @ngdoc method
10882 * @name $interpolateProvider#startSymbol
10883 * @description
10884 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10885 *
10886 * @param {string=} value new value to set the starting symbol to.
10887 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10888 */
10889 this.startSymbol = function(value) {
10890 if (value) {
10891 startSymbol = value;
10892 return this;
10893 } else {
10894 return startSymbol;
10895 }
10896 };
10897
10898 /**
10899 * @ngdoc method
10900 * @name $interpolateProvider#endSymbol
10901 * @description
10902 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10903 *
10904 * @param {string=} value new value to set the ending symbol to.
10905 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10906 */
10907 this.endSymbol = function(value) {
10908 if (value) {
10909 endSymbol = value;
10910 return this;
10911 } else {
10912 return endSymbol;
10913 }
10914 };
10915
10916
10917 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10918 var startSymbolLength = startSymbol.length,
10919 endSymbolLength = endSymbol.length,
10920 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
10921 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
10922
10923 function escape(ch) {
10924 return '\\\\\\' + ch;
10925 }
10926
10927 function unescapeText(text) {
10928 return text.replace(escapedStartRegexp, startSymbol).
10929 replace(escapedEndRegexp, endSymbol);
10930 }
10931
10932 function stringify(value) {
10933 if (value == null) { // null || undefined
10934 return '';
10935 }
10936 switch (typeof value) {
10937 case 'string':
10938 break;
10939 case 'number':
10940 value = '' + value;
10941 break;
10942 default:
10943 value = toJson(value);
10944 }
10945
10946 return value;
10947 }
10948
10949 /**
10950 * @ngdoc service
10951 * @name $interpolate
10952 * @kind function
10953 *
10954 * @requires $parse
10955 * @requires $sce
10956 *
10957 * @description
10958 *
10959 * Compiles a string with markup into an interpolation function. This service is used by the
10960 * HTML {@link ng.$compile $compile} service for data binding. See
10961 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
10962 * interpolation markup.
10963 *
10964 *
10965 * ```js
10966 * var $interpolate = ...; // injected
10967 * var exp = $interpolate('Hello {{name | uppercase}}!');
10968 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
10969 * ```
10970 *
10971 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
10972 * `true`, the interpolation function will return `undefined` unless all embedded expressions
10973 * evaluate to a value other than `undefined`.
10974 *
10975 * ```js
10976 * var $interpolate = ...; // injected
10977 * var context = {greeting: 'Hello', name: undefined };
10978 *
10979 * // default "forgiving" mode
10980 * var exp = $interpolate('{{greeting}} {{name}}!');
10981 * expect(exp(context)).toEqual('Hello !');
10982 *
10983 * // "allOrNothing" mode
10984 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
10985 * expect(exp(context)).toBeUndefined();
10986 * context.name = 'Angular';
10987 * expect(exp(context)).toEqual('Hello Angular!');
10988 * ```
10989 *
10990 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
10991 *
10992 * ####Escaped Interpolation
10993 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
10994 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
10995 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
10996 * or binding.
10997 *
10998 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
10999 * degree, while also enabling code examples to work without relying on the
11000 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11001 *
11002 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11003 * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
11004 * interpolation start/end markers with their escaped counterparts.**
11005 *
11006 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11007 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11008 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11009 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11010 * this is typically useful only when user-data is used in rendering a template from the server, or
11011 * when otherwise untrusted data is used by a directive.
11012 *
11013 * <example>
11014 * <file name="index.html">
11015 * <div ng-init="username='A user'">
11016 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11017 * </p>
11018 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11019 * application, but fails to accomplish their task, because the server has correctly
11020 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11021 * characters.</p>
11022 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11023 * from the database by an administrator.</p>
11024 * </div>
11025 * </file>
11026 * </example>
11027 *
11028 * @param {string} text The text with markup to interpolate.
11029 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11030 * embedded expression in order to return an interpolation function. Strings with no
11031 * embedded expression will return null for the interpolation function.
11032 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11033 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11034 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11035 * provides Strict Contextual Escaping for details.
11036 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11037 * unless all embedded expressions evaluate to a value other than `undefined`.
11038 * @returns {function(context)} an interpolation function which is used to compute the
11039 * interpolated string. The function has these parameters:
11040 *
11041 * - `context`: evaluation context for all expressions embedded in the interpolated text
11042 */
11043 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11044 allOrNothing = !!allOrNothing;
11045 var startIndex,
11046 endIndex,
11047 index = 0,
11048 expressions = [],
11049 parseFns = [],
11050 textLength = text.length,
11051 exp,
11052 concat = [],
11053 expressionPositions = [];
11054
11055 while (index < textLength) {
11056 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11057 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11058 if (index !== startIndex) {
11059 concat.push(unescapeText(text.substring(index, startIndex)));
11060 }
11061 exp = text.substring(startIndex + startSymbolLength, endIndex);
11062 expressions.push(exp);
11063 parseFns.push($parse(exp, parseStringifyInterceptor));
11064 index = endIndex + endSymbolLength;
11065 expressionPositions.push(concat.length);
11066 concat.push('');
11067 } else {
11068 // we did not find an interpolation, so we have to add the remainder to the separators array
11069 if (index !== textLength) {
11070 concat.push(unescapeText(text.substring(index)));
11071 }
11072 break;
11073 }
11074 }
11075
11076 // Concatenating expressions makes it hard to reason about whether some combination of
11077 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11078 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11079 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11080 // make it obvious that you bound the value to some user controlled value. This helps reduce
11081 // the load when auditing for XSS issues.
11082 if (trustedContext && concat.length > 1) {
11083 $interpolateMinErr.throwNoconcat(text);
11084 }
11085
11086 if (!mustHaveExpression || expressions.length) {
11087 var compute = function(values) {
11088 for (var i = 0, ii = expressions.length; i < ii; i++) {
11089 if (allOrNothing && isUndefined(values[i])) return;
11090 concat[expressionPositions[i]] = values[i];
11091 }
11092 return concat.join('');
11093 };
11094
11095 var getValue = function(value) {
11096 return trustedContext ?
11097 $sce.getTrusted(trustedContext, value) :
11098 $sce.valueOf(value);
11099 };
11100
11101 return extend(function interpolationFn(context) {
11102 var i = 0;
11103 var ii = expressions.length;
11104 var values = new Array(ii);
11105
11106 try {
11107 for (; i < ii; i++) {
11108 values[i] = parseFns[i](context);
11109 }
11110
11111 return compute(values);
11112 } catch (err) {
11113 $exceptionHandler($interpolateMinErr.interr(text, err));
11114 }
11115
11116 }, {
11117 // all of these properties are undocumented for now
11118 exp: text, //just for compatibility with regular watchers created via $watch
11119 expressions: expressions,
11120 $$watchDelegate: function(scope, listener) {
11121 var lastValue;
11122 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11123 var currValue = compute(values);
11124 if (isFunction(listener)) {
11125 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11126 }
11127 lastValue = currValue;
11128 });
11129 }
11130 });
11131 }
11132
11133 function parseStringifyInterceptor(value) {
11134 try {
11135 value = getValue(value);
11136 return allOrNothing && !isDefined(value) ? value : stringify(value);
11137 } catch (err) {
11138 $exceptionHandler($interpolateMinErr.interr(text, err));
11139 }
11140 }
11141 }
11142
11143
11144 /**
11145 * @ngdoc method
11146 * @name $interpolate#startSymbol
11147 * @description
11148 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11149 *
11150 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11151 * the symbol.
11152 *
11153 * @returns {string} start symbol.
11154 */
11155 $interpolate.startSymbol = function() {
11156 return startSymbol;
11157 };
11158
11159
11160 /**
11161 * @ngdoc method
11162 * @name $interpolate#endSymbol
11163 * @description
11164 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11165 *
11166 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11167 * the symbol.
11168 *
11169 * @returns {string} end symbol.
11170 */
11171 $interpolate.endSymbol = function() {
11172 return endSymbol;
11173 };
11174
11175 return $interpolate;
11176 }];
11177}
11178
11179function $IntervalProvider() {
11180 this.$get = ['$rootScope', '$window', '$q', '$$q',
11181 function($rootScope, $window, $q, $$q) {
11182 var intervals = {};
11183
11184
11185 /**
11186 * @ngdoc service
11187 * @name $interval
11188 *
11189 * @description
11190 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11191 * milliseconds.
11192 *
11193 * The return value of registering an interval function is a promise. This promise will be
11194 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11195 * run indefinitely if `count` is not defined. The value of the notification will be the
11196 * number of iterations that have run.
11197 * To cancel an interval, call `$interval.cancel(promise)`.
11198 *
11199 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11200 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11201 * time.
11202 *
11203 * <div class="alert alert-warning">
11204 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11205 * with them. In particular they are not automatically destroyed when a controller's scope or a
11206 * directive's element are destroyed.
11207 * You should take this into consideration and make sure to always cancel the interval at the
11208 * appropriate moment. See the example below for more details on how and when to do this.
11209 * </div>
11210 *
11211 * @param {function()} fn A function that should be called repeatedly.
11212 * @param {number} delay Number of milliseconds between each function call.
11213 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11214 * indefinitely.
11215 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11216 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11217 * @param {...*=} Pass additional parameters to the executed function.
11218 * @returns {promise} A promise which will be notified on each iteration.
11219 *
11220 * @example
11221 * <example module="intervalExample">
11222 * <file name="index.html">
11223 * <script>
11224 * angular.module('intervalExample', [])
11225 * .controller('ExampleController', ['$scope', '$interval',
11226 * function($scope, $interval) {
11227 * $scope.format = 'M/d/yy h:mm:ss a';
11228 * $scope.blood_1 = 100;
11229 * $scope.blood_2 = 120;
11230 *
11231 * var stop;
11232 * $scope.fight = function() {
11233 * // Don't start a new fight if we are already fighting
11234 * if ( angular.isDefined(stop) ) return;
11235 *
11236 * stop = $interval(function() {
11237 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11238 * $scope.blood_1 = $scope.blood_1 - 3;
11239 * $scope.blood_2 = $scope.blood_2 - 4;
11240 * } else {
11241 * $scope.stopFight();
11242 * }
11243 * }, 100);
11244 * };
11245 *
11246 * $scope.stopFight = function() {
11247 * if (angular.isDefined(stop)) {
11248 * $interval.cancel(stop);
11249 * stop = undefined;
11250 * }
11251 * };
11252 *
11253 * $scope.resetFight = function() {
11254 * $scope.blood_1 = 100;
11255 * $scope.blood_2 = 120;
11256 * };
11257 *
11258 * $scope.$on('$destroy', function() {
11259 * // Make sure that the interval is destroyed too
11260 * $scope.stopFight();
11261 * });
11262 * }])
11263 * // Register the 'myCurrentTime' directive factory method.
11264 * // We inject $interval and dateFilter service since the factory method is DI.
11265 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11266 * function($interval, dateFilter) {
11267 * // return the directive link function. (compile function not needed)
11268 * return function(scope, element, attrs) {
11269 * var format, // date format
11270 * stopTime; // so that we can cancel the time updates
11271 *
11272 * // used to update the UI
11273 * function updateTime() {
11274 * element.text(dateFilter(new Date(), format));
11275 * }
11276 *
11277 * // watch the expression, and update the UI on change.
11278 * scope.$watch(attrs.myCurrentTime, function(value) {
11279 * format = value;
11280 * updateTime();
11281 * });
11282 *
11283 * stopTime = $interval(updateTime, 1000);
11284 *
11285 * // listen on DOM destroy (removal) event, and cancel the next UI update
11286 * // to prevent updating time after the DOM element was removed.
11287 * element.on('$destroy', function() {
11288 * $interval.cancel(stopTime);
11289 * });
11290 * }
11291 * }]);
11292 * </script>
11293 *
11294 * <div>
11295 * <div ng-controller="ExampleController">
11296 * <label>Date format: <input ng-model="format"></label> <hr/>
11297 * Current time is: <span my-current-time="format"></span>
11298 * <hr/>
11299 * Blood 1 : <font color='red'>{{blood_1}}</font>
11300 * Blood 2 : <font color='red'>{{blood_2}}</font>
11301 * <button type="button" data-ng-click="fight()">Fight</button>
11302 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11303 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11304 * </div>
11305 * </div>
11306 *
11307 * </file>
11308 * </example>
11309 */
11310 function interval(fn, delay, count, invokeApply) {
11311 var hasParams = arguments.length > 4,
11312 args = hasParams ? sliceArgs(arguments, 4) : [],
11313 setInterval = $window.setInterval,
11314 clearInterval = $window.clearInterval,
11315 iteration = 0,
11316 skipApply = (isDefined(invokeApply) && !invokeApply),
11317 deferred = (skipApply ? $$q : $q).defer(),
11318 promise = deferred.promise;
11319
11320 count = isDefined(count) ? count : 0;
11321
11322 promise.then(null, null, (!hasParams) ? fn : function() {
11323 fn.apply(null, args);
11324 });
11325
11326 promise.$$intervalId = setInterval(function tick() {
11327 deferred.notify(iteration++);
11328
11329 if (count > 0 && iteration >= count) {
11330 deferred.resolve(iteration);
11331 clearInterval(promise.$$intervalId);
11332 delete intervals[promise.$$intervalId];
11333 }
11334
11335 if (!skipApply) $rootScope.$apply();
11336
11337 }, delay);
11338
11339 intervals[promise.$$intervalId] = deferred;
11340
11341 return promise;
11342 }
11343
11344
11345 /**
11346 * @ngdoc method
11347 * @name $interval#cancel
11348 *
11349 * @description
11350 * Cancels a task associated with the `promise`.
11351 *
11352 * @param {Promise=} promise returned by the `$interval` function.
11353 * @returns {boolean} Returns `true` if the task was successfully canceled.
11354 */
11355 interval.cancel = function(promise) {
11356 if (promise && promise.$$intervalId in intervals) {
11357 intervals[promise.$$intervalId].reject('canceled');
11358 $window.clearInterval(promise.$$intervalId);
11359 delete intervals[promise.$$intervalId];
11360 return true;
11361 }
11362 return false;
11363 };
11364
11365 return interval;
11366 }];
11367}
11368
11369/**
11370 * @ngdoc service
11371 * @name $locale
11372 *
11373 * @description
11374 * $locale service provides localization rules for various Angular components. As of right now the
11375 * only public api is:
11376 *
11377 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11378 */
11379
11380var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11381 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11382var $locationMinErr = minErr('$location');
11383
11384
11385/**
11386 * Encode path using encodeUriSegment, ignoring forward slashes
11387 *
11388 * @param {string} path Path to encode
11389 * @returns {string}
11390 */
11391function encodePath(path) {
11392 var segments = path.split('/'),
11393 i = segments.length;
11394
11395 while (i--) {
11396 segments[i] = encodeUriSegment(segments[i]);
11397 }
11398
11399 return segments.join('/');
11400}
11401
11402function parseAbsoluteUrl(absoluteUrl, locationObj) {
11403 var parsedUrl = urlResolve(absoluteUrl);
11404
11405 locationObj.$$protocol = parsedUrl.protocol;
11406 locationObj.$$host = parsedUrl.hostname;
11407 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11408}
11409
11410
11411function parseAppUrl(relativeUrl, locationObj) {
11412 var prefixed = (relativeUrl.charAt(0) !== '/');
11413 if (prefixed) {
11414 relativeUrl = '/' + relativeUrl;
11415 }
11416 var match = urlResolve(relativeUrl);
11417 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11418 match.pathname.substring(1) : match.pathname);
11419 locationObj.$$search = parseKeyValue(match.search);
11420 locationObj.$$hash = decodeURIComponent(match.hash);
11421
11422 // make sure path starts with '/';
11423 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11424 locationObj.$$path = '/' + locationObj.$$path;
11425 }
11426}
11427
11428
11429/**
11430 *
11431 * @param {string} begin
11432 * @param {string} whole
11433 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11434 * expected string.
11435 */
11436function beginsWith(begin, whole) {
11437 if (whole.indexOf(begin) === 0) {
11438 return whole.substr(begin.length);
11439 }
11440}
11441
11442
11443function stripHash(url) {
11444 var index = url.indexOf('#');
11445 return index == -1 ? url : url.substr(0, index);
11446}
11447
11448function trimEmptyHash(url) {
11449 return url.replace(/(#.+)|#$/, '$1');
11450}
11451
11452
11453function stripFile(url) {
11454 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11455}
11456
11457/* return the server only (scheme://host:port) */
11458function serverBase(url) {
11459 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11460}
11461
11462
11463/**
11464 * LocationHtml5Url represents an url
11465 * This object is exposed as $location service when HTML5 mode is enabled and supported
11466 *
11467 * @constructor
11468 * @param {string} appBase application base URL
11469 * @param {string} appBaseNoFile application base URL stripped of any filename
11470 * @param {string} basePrefix url path prefix
11471 */
11472function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11473 this.$$html5 = true;
11474 basePrefix = basePrefix || '';
11475 parseAbsoluteUrl(appBase, this);
11476
11477
11478 /**
11479 * Parse given html5 (regular) url string into properties
11480 * @param {string} url HTML5 url
11481 * @private
11482 */
11483 this.$$parse = function(url) {
11484 var pathUrl = beginsWith(appBaseNoFile, url);
11485 if (!isString(pathUrl)) {
11486 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11487 appBaseNoFile);
11488 }
11489
11490 parseAppUrl(pathUrl, this);
11491
11492 if (!this.$$path) {
11493 this.$$path = '/';
11494 }
11495
11496 this.$$compose();
11497 };
11498
11499 /**
11500 * Compose url and update `absUrl` property
11501 * @private
11502 */
11503 this.$$compose = function() {
11504 var search = toKeyValue(this.$$search),
11505 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11506
11507 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11508 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11509 };
11510
11511 this.$$parseLinkUrl = function(url, relHref) {
11512 if (relHref && relHref[0] === '#') {
11513 // special case for links to hash fragments:
11514 // keep the old url and only replace the hash fragment
11515 this.hash(relHref.slice(1));
11516 return true;
11517 }
11518 var appUrl, prevAppUrl;
11519 var rewrittenUrl;
11520
11521 if (isDefined(appUrl = beginsWith(appBase, url))) {
11522 prevAppUrl = appUrl;
11523 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11524 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11525 } else {
11526 rewrittenUrl = appBase + prevAppUrl;
11527 }
11528 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11529 rewrittenUrl = appBaseNoFile + appUrl;
11530 } else if (appBaseNoFile == url + '/') {
11531 rewrittenUrl = appBaseNoFile;
11532 }
11533 if (rewrittenUrl) {
11534 this.$$parse(rewrittenUrl);
11535 }
11536 return !!rewrittenUrl;
11537 };
11538}
11539
11540
11541/**
11542 * LocationHashbangUrl represents url
11543 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11544 * It also serves as the base class for html5 mode fallback on legacy browsers.
11545 *
11546 * @constructor
11547 * @param {string} appBase application base URL
11548 * @param {string} appBaseNoFile application base URL stripped of any filename
11549 * @param {string} hashPrefix hashbang prefix
11550 */
11551function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11552
11553 parseAbsoluteUrl(appBase, this);
11554
11555
11556 /**
11557 * Parse given hashbang url into properties
11558 * @param {string} url Hashbang url
11559 * @private
11560 */
11561 this.$$parse = function(url) {
11562 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11563 var withoutHashUrl;
11564
11565 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11566
11567 // The rest of the url starts with a hash so we have
11568 // got either a hashbang path or a plain hash fragment
11569 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11570 if (isUndefined(withoutHashUrl)) {
11571 // There was no hashbang prefix so we just have a hash fragment
11572 withoutHashUrl = withoutBaseUrl;
11573 }
11574
11575 } else {
11576 // There was no hashbang path nor hash fragment:
11577 // If we are in HTML5 mode we use what is left as the path;
11578 // Otherwise we ignore what is left
11579 if (this.$$html5) {
11580 withoutHashUrl = withoutBaseUrl;
11581 } else {
11582 withoutHashUrl = '';
11583 if (isUndefined(withoutBaseUrl)) {
11584 appBase = url;
11585 this.replace();
11586 }
11587 }
11588 }
11589
11590 parseAppUrl(withoutHashUrl, this);
11591
11592 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11593
11594 this.$$compose();
11595
11596 /*
11597 * In Windows, on an anchor node on documents loaded from
11598 * the filesystem, the browser will return a pathname
11599 * prefixed with the drive name ('/C:/path') when a
11600 * pathname without a drive is set:
11601 * * a.setAttribute('href', '/foo')
11602 * * a.pathname === '/C:/foo' //true
11603 *
11604 * Inside of Angular, we're always using pathnames that
11605 * do not include drive names for routing.
11606 */
11607 function removeWindowsDriveName(path, url, base) {
11608 /*
11609 Matches paths for file protocol on windows,
11610 such as /C:/foo/bar, and captures only /foo/bar.
11611 */
11612 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11613
11614 var firstPathSegmentMatch;
11615
11616 //Get the relative path from the input URL.
11617 if (url.indexOf(base) === 0) {
11618 url = url.replace(base, '');
11619 }
11620
11621 // The input URL intentionally contains a first path segment that ends with a colon.
11622 if (windowsFilePathExp.exec(url)) {
11623 return path;
11624 }
11625
11626 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11627 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11628 }
11629 };
11630
11631 /**
11632 * Compose hashbang url and update `absUrl` property
11633 * @private
11634 */
11635 this.$$compose = function() {
11636 var search = toKeyValue(this.$$search),
11637 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11638
11639 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11640 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11641 };
11642
11643 this.$$parseLinkUrl = function(url, relHref) {
11644 if (stripHash(appBase) == stripHash(url)) {
11645 this.$$parse(url);
11646 return true;
11647 }
11648 return false;
11649 };
11650}
11651
11652
11653/**
11654 * LocationHashbangUrl represents url
11655 * This object is exposed as $location service when html5 history api is enabled but the browser
11656 * does not support it.
11657 *
11658 * @constructor
11659 * @param {string} appBase application base URL
11660 * @param {string} appBaseNoFile application base URL stripped of any filename
11661 * @param {string} hashPrefix hashbang prefix
11662 */
11663function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11664 this.$$html5 = true;
11665 LocationHashbangUrl.apply(this, arguments);
11666
11667 this.$$parseLinkUrl = function(url, relHref) {
11668 if (relHref && relHref[0] === '#') {
11669 // special case for links to hash fragments:
11670 // keep the old url and only replace the hash fragment
11671 this.hash(relHref.slice(1));
11672 return true;
11673 }
11674
11675 var rewrittenUrl;
11676 var appUrl;
11677
11678 if (appBase == stripHash(url)) {
11679 rewrittenUrl = url;
11680 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11681 rewrittenUrl = appBase + hashPrefix + appUrl;
11682 } else if (appBaseNoFile === url + '/') {
11683 rewrittenUrl = appBaseNoFile;
11684 }
11685 if (rewrittenUrl) {
11686 this.$$parse(rewrittenUrl);
11687 }
11688 return !!rewrittenUrl;
11689 };
11690
11691 this.$$compose = function() {
11692 var search = toKeyValue(this.$$search),
11693 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11694
11695 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11696 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11697 this.$$absUrl = appBase + hashPrefix + this.$$url;
11698 };
11699
11700}
11701
11702
11703var locationPrototype = {
11704
11705 /**
11706 * Are we in html5 mode?
11707 * @private
11708 */
11709 $$html5: false,
11710
11711 /**
11712 * Has any change been replacing?
11713 * @private
11714 */
11715 $$replace: false,
11716
11717 /**
11718 * @ngdoc method
11719 * @name $location#absUrl
11720 *
11721 * @description
11722 * This method is getter only.
11723 *
11724 * Return full url representation with all segments encoded according to rules specified in
11725 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11726 *
11727 *
11728 * ```js
11729 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11730 * var absUrl = $location.absUrl();
11731 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11732 * ```
11733 *
11734 * @return {string} full url
11735 */
11736 absUrl: locationGetter('$$absUrl'),
11737
11738 /**
11739 * @ngdoc method
11740 * @name $location#url
11741 *
11742 * @description
11743 * This method is getter / setter.
11744 *
11745 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11746 *
11747 * Change path, search and hash, when called with parameter and return `$location`.
11748 *
11749 *
11750 * ```js
11751 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11752 * var url = $location.url();
11753 * // => "/some/path?foo=bar&baz=xoxo"
11754 * ```
11755 *
11756 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11757 * @return {string} url
11758 */
11759 url: function(url) {
11760 if (isUndefined(url)) {
11761 return this.$$url;
11762 }
11763
11764 var match = PATH_MATCH.exec(url);
11765 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11766 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11767 this.hash(match[5] || '');
11768
11769 return this;
11770 },
11771
11772 /**
11773 * @ngdoc method
11774 * @name $location#protocol
11775 *
11776 * @description
11777 * This method is getter only.
11778 *
11779 * Return protocol of current url.
11780 *
11781 *
11782 * ```js
11783 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11784 * var protocol = $location.protocol();
11785 * // => "http"
11786 * ```
11787 *
11788 * @return {string} protocol of current url
11789 */
11790 protocol: locationGetter('$$protocol'),
11791
11792 /**
11793 * @ngdoc method
11794 * @name $location#host
11795 *
11796 * @description
11797 * This method is getter only.
11798 *
11799 * Return host of current url.
11800 *
11801 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11802 *
11803 *
11804 * ```js
11805 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11806 * var host = $location.host();
11807 * // => "example.com"
11808 *
11809 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11810 * host = $location.host();
11811 * // => "example.com"
11812 * host = location.host;
11813 * // => "example.com:8080"
11814 * ```
11815 *
11816 * @return {string} host of current url.
11817 */
11818 host: locationGetter('$$host'),
11819
11820 /**
11821 * @ngdoc method
11822 * @name $location#port
11823 *
11824 * @description
11825 * This method is getter only.
11826 *
11827 * Return port of current url.
11828 *
11829 *
11830 * ```js
11831 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11832 * var port = $location.port();
11833 * // => 80
11834 * ```
11835 *
11836 * @return {Number} port
11837 */
11838 port: locationGetter('$$port'),
11839
11840 /**
11841 * @ngdoc method
11842 * @name $location#path
11843 *
11844 * @description
11845 * This method is getter / setter.
11846 *
11847 * Return path of current url when called without any parameter.
11848 *
11849 * Change path when called with parameter and return `$location`.
11850 *
11851 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11852 * if it is missing.
11853 *
11854 *
11855 * ```js
11856 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11857 * var path = $location.path();
11858 * // => "/some/path"
11859 * ```
11860 *
11861 * @param {(string|number)=} path New path
11862 * @return {string} path
11863 */
11864 path: locationGetterSetter('$$path', function(path) {
11865 path = path !== null ? path.toString() : '';
11866 return path.charAt(0) == '/' ? path : '/' + path;
11867 }),
11868
11869 /**
11870 * @ngdoc method
11871 * @name $location#search
11872 *
11873 * @description
11874 * This method is getter / setter.
11875 *
11876 * Return search part (as object) of current url when called without any parameter.
11877 *
11878 * Change search part when called with parameter and return `$location`.
11879 *
11880 *
11881 * ```js
11882 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11883 * var searchObject = $location.search();
11884 * // => {foo: 'bar', baz: 'xoxo'}
11885 *
11886 * // set foo to 'yipee'
11887 * $location.search('foo', 'yipee');
11888 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11889 * ```
11890 *
11891 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11892 * hash object.
11893 *
11894 * When called with a single argument the method acts as a setter, setting the `search` component
11895 * of `$location` to the specified value.
11896 *
11897 * If the argument is a hash object containing an array of values, these values will be encoded
11898 * as duplicate search parameters in the url.
11899 *
11900 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11901 * will override only a single search property.
11902 *
11903 * If `paramValue` is an array, it will override the property of the `search` component of
11904 * `$location` specified via the first argument.
11905 *
11906 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11907 *
11908 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11909 * value nor trailing equal sign.
11910 *
11911 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11912 * one or more arguments returns `$location` object itself.
11913 */
11914 search: function(search, paramValue) {
11915 switch (arguments.length) {
11916 case 0:
11917 return this.$$search;
11918 case 1:
11919 if (isString(search) || isNumber(search)) {
11920 search = search.toString();
11921 this.$$search = parseKeyValue(search);
11922 } else if (isObject(search)) {
11923 search = copy(search, {});
11924 // remove object undefined or null properties
11925 forEach(search, function(value, key) {
11926 if (value == null) delete search[key];
11927 });
11928
11929 this.$$search = search;
11930 } else {
11931 throw $locationMinErr('isrcharg',
11932 'The first argument of the `$location#search()` call must be a string or an object.');
11933 }
11934 break;
11935 default:
11936 if (isUndefined(paramValue) || paramValue === null) {
11937 delete this.$$search[search];
11938 } else {
11939 this.$$search[search] = paramValue;
11940 }
11941 }
11942
11943 this.$$compose();
11944 return this;
11945 },
11946
11947 /**
11948 * @ngdoc method
11949 * @name $location#hash
11950 *
11951 * @description
11952 * This method is getter / setter.
11953 *
11954 * Returns the hash fragment when called without any parameters.
11955 *
11956 * Changes the hash fragment when called with a parameter and returns `$location`.
11957 *
11958 *
11959 * ```js
11960 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
11961 * var hash = $location.hash();
11962 * // => "hashValue"
11963 * ```
11964 *
11965 * @param {(string|number)=} hash New hash fragment
11966 * @return {string} hash
11967 */
11968 hash: locationGetterSetter('$$hash', function(hash) {
11969 return hash !== null ? hash.toString() : '';
11970 }),
11971
11972 /**
11973 * @ngdoc method
11974 * @name $location#replace
11975 *
11976 * @description
11977 * If called, all changes to $location during the current `$digest` will replace the current history
11978 * record, instead of adding a new one.
11979 */
11980 replace: function() {
11981 this.$$replace = true;
11982 return this;
11983 }
11984};
11985
11986forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
11987 Location.prototype = Object.create(locationPrototype);
11988
11989 /**
11990 * @ngdoc method
11991 * @name $location#state
11992 *
11993 * @description
11994 * This method is getter / setter.
11995 *
11996 * Return the history state object when called without any parameter.
11997 *
11998 * Change the history state object when called with one parameter and return `$location`.
11999 * The state object is later passed to `pushState` or `replaceState`.
12000 *
12001 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12002 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12003 * older browsers (like IE9 or Android < 4.0), don't use this method.
12004 *
12005 * @param {object=} state State object for pushState or replaceState
12006 * @return {object} state
12007 */
12008 Location.prototype.state = function(state) {
12009 if (!arguments.length) {
12010 return this.$$state;
12011 }
12012
12013 if (Location !== LocationHtml5Url || !this.$$html5) {
12014 throw $locationMinErr('nostate', 'History API state support is available only ' +
12015 'in HTML5 mode and only in browsers supporting HTML5 History API');
12016 }
12017 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12018 // but we're changing the $$state reference to $browser.state() during the $digest
12019 // so the modification window is narrow.
12020 this.$$state = isUndefined(state) ? null : state;
12021
12022 return this;
12023 };
12024});
12025
12026
12027function locationGetter(property) {
12028 return function() {
12029 return this[property];
12030 };
12031}
12032
12033
12034function locationGetterSetter(property, preprocess) {
12035 return function(value) {
12036 if (isUndefined(value)) {
12037 return this[property];
12038 }
12039
12040 this[property] = preprocess(value);
12041 this.$$compose();
12042
12043 return this;
12044 };
12045}
12046
12047
12048/**
12049 * @ngdoc service
12050 * @name $location
12051 *
12052 * @requires $rootElement
12053 *
12054 * @description
12055 * The $location service parses the URL in the browser address bar (based on the
12056 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12057 * available to your application. Changes to the URL in the address bar are reflected into
12058 * $location service and changes to $location are reflected into the browser address bar.
12059 *
12060 * **The $location service:**
12061 *
12062 * - Exposes the current URL in the browser address bar, so you can
12063 * - Watch and observe the URL.
12064 * - Change the URL.
12065 * - Synchronizes the URL with the browser when the user
12066 * - Changes the address bar.
12067 * - Clicks the back or forward button (or clicks a History link).
12068 * - Clicks on a link.
12069 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12070 *
12071 * For more information see {@link guide/$location Developer Guide: Using $location}
12072 */
12073
12074/**
12075 * @ngdoc provider
12076 * @name $locationProvider
12077 * @description
12078 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12079 */
12080function $LocationProvider() {
12081 var hashPrefix = '',
12082 html5Mode = {
12083 enabled: false,
12084 requireBase: true,
12085 rewriteLinks: true
12086 };
12087
12088 /**
12089 * @ngdoc method
12090 * @name $locationProvider#hashPrefix
12091 * @description
12092 * @param {string=} prefix Prefix for hash part (containing path and search)
12093 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12094 */
12095 this.hashPrefix = function(prefix) {
12096 if (isDefined(prefix)) {
12097 hashPrefix = prefix;
12098 return this;
12099 } else {
12100 return hashPrefix;
12101 }
12102 };
12103
12104 /**
12105 * @ngdoc method
12106 * @name $locationProvider#html5Mode
12107 * @description
12108 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12109 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12110 * properties:
12111 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12112 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12113 * support `pushState`.
12114 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12115 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12116 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12117 * See the {@link guide/$location $location guide for more information}
12118 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12119 * enables/disables url rewriting for relative links.
12120 *
12121 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12122 */
12123 this.html5Mode = function(mode) {
12124 if (isBoolean(mode)) {
12125 html5Mode.enabled = mode;
12126 return this;
12127 } else if (isObject(mode)) {
12128
12129 if (isBoolean(mode.enabled)) {
12130 html5Mode.enabled = mode.enabled;
12131 }
12132
12133 if (isBoolean(mode.requireBase)) {
12134 html5Mode.requireBase = mode.requireBase;
12135 }
12136
12137 if (isBoolean(mode.rewriteLinks)) {
12138 html5Mode.rewriteLinks = mode.rewriteLinks;
12139 }
12140
12141 return this;
12142 } else {
12143 return html5Mode;
12144 }
12145 };
12146
12147 /**
12148 * @ngdoc event
12149 * @name $location#$locationChangeStart
12150 * @eventType broadcast on root scope
12151 * @description
12152 * Broadcasted before a URL will change.
12153 *
12154 * This change can be prevented by calling
12155 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12156 * details about event object. Upon successful change
12157 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12158 *
12159 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12160 * the browser supports the HTML5 History API.
12161 *
12162 * @param {Object} angularEvent Synthetic event object.
12163 * @param {string} newUrl New URL
12164 * @param {string=} oldUrl URL that was before it was changed.
12165 * @param {string=} newState New history state object
12166 * @param {string=} oldState History state object that was before it was changed.
12167 */
12168
12169 /**
12170 * @ngdoc event
12171 * @name $location#$locationChangeSuccess
12172 * @eventType broadcast on root scope
12173 * @description
12174 * Broadcasted after a URL was changed.
12175 *
12176 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12177 * the browser supports the HTML5 History API.
12178 *
12179 * @param {Object} angularEvent Synthetic event object.
12180 * @param {string} newUrl New URL
12181 * @param {string=} oldUrl URL that was before it was changed.
12182 * @param {string=} newState New history state object
12183 * @param {string=} oldState History state object that was before it was changed.
12184 */
12185
12186 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12187 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12188 var $location,
12189 LocationMode,
12190 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12191 initialUrl = $browser.url(),
12192 appBase;
12193
12194 if (html5Mode.enabled) {
12195 if (!baseHref && html5Mode.requireBase) {
12196 throw $locationMinErr('nobase',
12197 "$location in HTML5 mode requires a <base> tag to be present!");
12198 }
12199 appBase = serverBase(initialUrl) + (baseHref || '/');
12200 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12201 } else {
12202 appBase = stripHash(initialUrl);
12203 LocationMode = LocationHashbangUrl;
12204 }
12205 var appBaseNoFile = stripFile(appBase);
12206
12207 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12208 $location.$$parseLinkUrl(initialUrl, initialUrl);
12209
12210 $location.$$state = $browser.state();
12211
12212 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12213
12214 function setBrowserUrlWithFallback(url, replace, state) {
12215 var oldUrl = $location.url();
12216 var oldState = $location.$$state;
12217 try {
12218 $browser.url(url, replace, state);
12219
12220 // Make sure $location.state() returns referentially identical (not just deeply equal)
12221 // state object; this makes possible quick checking if the state changed in the digest
12222 // loop. Checking deep equality would be too expensive.
12223 $location.$$state = $browser.state();
12224 } catch (e) {
12225 // Restore old values if pushState fails
12226 $location.url(oldUrl);
12227 $location.$$state = oldState;
12228
12229 throw e;
12230 }
12231 }
12232
12233 $rootElement.on('click', function(event) {
12234 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12235 // currently we open nice url link and redirect then
12236
12237 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12238
12239 var elm = jqLite(event.target);
12240
12241 // traverse the DOM up to find first A tag
12242 while (nodeName_(elm[0]) !== 'a') {
12243 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12244 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12245 }
12246
12247 var absHref = elm.prop('href');
12248 // get the actual href attribute - see
12249 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12250 var relHref = elm.attr('href') || elm.attr('xlink:href');
12251
12252 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12253 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12254 // an animation.
12255 absHref = urlResolve(absHref.animVal).href;
12256 }
12257
12258 // Ignore when url is started with javascript: or mailto:
12259 if (IGNORE_URI_REGEXP.test(absHref)) return;
12260
12261 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12262 if ($location.$$parseLinkUrl(absHref, relHref)) {
12263 // We do a preventDefault for all urls that are part of the angular application,
12264 // in html5mode and also without, so that we are able to abort navigation without
12265 // getting double entries in the location history.
12266 event.preventDefault();
12267 // update location manually
12268 if ($location.absUrl() != $browser.url()) {
12269 $rootScope.$apply();
12270 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12271 $window.angular['ff-684208-preventDefault'] = true;
12272 }
12273 }
12274 }
12275 });
12276
12277
12278 // rewrite hashbang url <> html5 url
12279 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12280 $browser.url($location.absUrl(), true);
12281 }
12282
12283 var initializing = true;
12284
12285 // update $location when $browser url changes
12286 $browser.onUrlChange(function(newUrl, newState) {
12287
12288 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12289 // If we are navigating outside of the app then force a reload
12290 $window.location.href = newUrl;
12291 return;
12292 }
12293
12294 $rootScope.$evalAsync(function() {
12295 var oldUrl = $location.absUrl();
12296 var oldState = $location.$$state;
12297 var defaultPrevented;
12298 newUrl = trimEmptyHash(newUrl);
12299 $location.$$parse(newUrl);
12300 $location.$$state = newState;
12301
12302 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12303 newState, oldState).defaultPrevented;
12304
12305 // if the location was changed by a `$locationChangeStart` handler then stop
12306 // processing this location change
12307 if ($location.absUrl() !== newUrl) return;
12308
12309 if (defaultPrevented) {
12310 $location.$$parse(oldUrl);
12311 $location.$$state = oldState;
12312 setBrowserUrlWithFallback(oldUrl, false, oldState);
12313 } else {
12314 initializing = false;
12315 afterLocationChange(oldUrl, oldState);
12316 }
12317 });
12318 if (!$rootScope.$$phase) $rootScope.$digest();
12319 });
12320
12321 // update browser
12322 $rootScope.$watch(function $locationWatch() {
12323 var oldUrl = trimEmptyHash($browser.url());
12324 var newUrl = trimEmptyHash($location.absUrl());
12325 var oldState = $browser.state();
12326 var currentReplace = $location.$$replace;
12327 var urlOrStateChanged = oldUrl !== newUrl ||
12328 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12329
12330 if (initializing || urlOrStateChanged) {
12331 initializing = false;
12332
12333 $rootScope.$evalAsync(function() {
12334 var newUrl = $location.absUrl();
12335 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12336 $location.$$state, oldState).defaultPrevented;
12337
12338 // if the location was changed by a `$locationChangeStart` handler then stop
12339 // processing this location change
12340 if ($location.absUrl() !== newUrl) return;
12341
12342 if (defaultPrevented) {
12343 $location.$$parse(oldUrl);
12344 $location.$$state = oldState;
12345 } else {
12346 if (urlOrStateChanged) {
12347 setBrowserUrlWithFallback(newUrl, currentReplace,
12348 oldState === $location.$$state ? null : $location.$$state);
12349 }
12350 afterLocationChange(oldUrl, oldState);
12351 }
12352 });
12353 }
12354
12355 $location.$$replace = false;
12356
12357 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12358 // there is a change
12359 });
12360
12361 return $location;
12362
12363 function afterLocationChange(oldUrl, oldState) {
12364 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12365 $location.$$state, oldState);
12366 }
12367}];
12368}
12369
12370/**
12371 * @ngdoc service
12372 * @name $log
12373 * @requires $window
12374 *
12375 * @description
12376 * Simple service for logging. Default implementation safely writes the message
12377 * into the browser's console (if present).
12378 *
12379 * The main purpose of this service is to simplify debugging and troubleshooting.
12380 *
12381 * The default is to log `debug` messages. You can use
12382 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12383 *
12384 * @example
12385 <example module="logExample">
12386 <file name="script.js">
12387 angular.module('logExample', [])
12388 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12389 $scope.$log = $log;
12390 $scope.message = 'Hello World!';
12391 }]);
12392 </file>
12393 <file name="index.html">
12394 <div ng-controller="LogController">
12395 <p>Reload this page with open console, enter text and hit the log button...</p>
12396 <label>Message:
12397 <input type="text" ng-model="message" /></label>
12398 <button ng-click="$log.log(message)">log</button>
12399 <button ng-click="$log.warn(message)">warn</button>
12400 <button ng-click="$log.info(message)">info</button>
12401 <button ng-click="$log.error(message)">error</button>
12402 <button ng-click="$log.debug(message)">debug</button>
12403 </div>
12404 </file>
12405 </example>
12406 */
12407
12408/**
12409 * @ngdoc provider
12410 * @name $logProvider
12411 * @description
12412 * Use the `$logProvider` to configure how the application logs messages
12413 */
12414function $LogProvider() {
12415 var debug = true,
12416 self = this;
12417
12418 /**
12419 * @ngdoc method
12420 * @name $logProvider#debugEnabled
12421 * @description
12422 * @param {boolean=} flag enable or disable debug level messages
12423 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12424 */
12425 this.debugEnabled = function(flag) {
12426 if (isDefined(flag)) {
12427 debug = flag;
12428 return this;
12429 } else {
12430 return debug;
12431 }
12432 };
12433
12434 this.$get = ['$window', function($window) {
12435 return {
12436 /**
12437 * @ngdoc method
12438 * @name $log#log
12439 *
12440 * @description
12441 * Write a log message
12442 */
12443 log: consoleLog('log'),
12444
12445 /**
12446 * @ngdoc method
12447 * @name $log#info
12448 *
12449 * @description
12450 * Write an information message
12451 */
12452 info: consoleLog('info'),
12453
12454 /**
12455 * @ngdoc method
12456 * @name $log#warn
12457 *
12458 * @description
12459 * Write a warning message
12460 */
12461 warn: consoleLog('warn'),
12462
12463 /**
12464 * @ngdoc method
12465 * @name $log#error
12466 *
12467 * @description
12468 * Write an error message
12469 */
12470 error: consoleLog('error'),
12471
12472 /**
12473 * @ngdoc method
12474 * @name $log#debug
12475 *
12476 * @description
12477 * Write a debug message
12478 */
12479 debug: (function() {
12480 var fn = consoleLog('debug');
12481
12482 return function() {
12483 if (debug) {
12484 fn.apply(self, arguments);
12485 }
12486 };
12487 }())
12488 };
12489
12490 function formatError(arg) {
12491 if (arg instanceof Error) {
12492 if (arg.stack) {
12493 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12494 ? 'Error: ' + arg.message + '\n' + arg.stack
12495 : arg.stack;
12496 } else if (arg.sourceURL) {
12497 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12498 }
12499 }
12500 return arg;
12501 }
12502
12503 function consoleLog(type) {
12504 var console = $window.console || {},
12505 logFn = console[type] || console.log || noop,
12506 hasApply = false;
12507
12508 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12509 // The reason behind this is that console.log has type "object" in IE8...
12510 try {
12511 hasApply = !!logFn.apply;
12512 } catch (e) {}
12513
12514 if (hasApply) {
12515 return function() {
12516 var args = [];
12517 forEach(arguments, function(arg) {
12518 args.push(formatError(arg));
12519 });
12520 return logFn.apply(console, args);
12521 };
12522 }
12523
12524 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12525 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12526 return function(arg1, arg2) {
12527 logFn(arg1, arg2 == null ? '' : arg2);
12528 };
12529 }
12530 }];
12531}
12532
12533/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12534 * Any commits to this file should be reviewed with security in mind. *
12535 * Changes to this file can potentially create security vulnerabilities. *
12536 * An approval from 2 Core members with history of modifying *
12537 * this file is required. *
12538 * *
12539 * Does the change somehow allow for arbitrary javascript to be executed? *
12540 * Or allows for someone to change the prototype of built-in objects? *
12541 * Or gives undesired access to variables likes document or window? *
12542 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12543
12544var $parseMinErr = minErr('$parse');
12545
12546// Sandboxing Angular Expressions
12547// ------------------------------
12548// Angular expressions are generally considered safe because these expressions only have direct
12549// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12550// obtaining a reference to native JS functions such as the Function constructor.
12551//
12552// As an example, consider the following Angular expression:
12553//
12554// {}.toString.constructor('alert("evil JS code")')
12555//
12556// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12557// against the expression language, but not to prevent exploits that were enabled by exposing
12558// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12559// practice and therefore we are not even trying to protect against interaction with an object
12560// explicitly exposed in this way.
12561//
12562// In general, it is not possible to access a Window object from an angular expression unless a
12563// window or some DOM object that has a reference to window is published onto a Scope.
12564// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12565// native objects.
12566//
12567// See https://docs.angularjs.org/guide/security
12568
12569
12570function ensureSafeMemberName(name, fullExpression) {
12571 if (name === "__defineGetter__" || name === "__defineSetter__"
12572 || name === "__lookupGetter__" || name === "__lookupSetter__"
12573 || name === "__proto__") {
12574 throw $parseMinErr('isecfld',
12575 'Attempting to access a disallowed field in Angular expressions! '
12576 + 'Expression: {0}', fullExpression);
12577 }
12578 return name;
12579}
12580
12581function getStringValue(name, fullExpression) {
12582 // From the JavaScript docs:
12583 // Property names must be strings. This means that non-string objects cannot be used
12584 // as keys in an object. Any non-string object, including a number, is typecasted
12585 // into a string via the toString method.
12586 //
12587 // So, to ensure that we are checking the same `name` that JavaScript would use,
12588 // we cast it to a string, if possible.
12589 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12590 // this is, this will handle objects that misbehave.
12591 name = name + '';
12592 if (!isString(name)) {
12593 throw $parseMinErr('iseccst',
12594 'Cannot convert object to primitive value! '
12595 + 'Expression: {0}', fullExpression);
12596 }
12597 return name;
12598}
12599
12600function ensureSafeObject(obj, fullExpression) {
12601 // nifty check if obj is Function that is fast and works across iframes and other contexts
12602 if (obj) {
12603 if (obj.constructor === obj) {
12604 throw $parseMinErr('isecfn',
12605 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12606 fullExpression);
12607 } else if (// isWindow(obj)
12608 obj.window === obj) {
12609 throw $parseMinErr('isecwindow',
12610 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12611 fullExpression);
12612 } else if (// isElement(obj)
12613 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12614 throw $parseMinErr('isecdom',
12615 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12616 fullExpression);
12617 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12618 obj === Object) {
12619 throw $parseMinErr('isecobj',
12620 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12621 fullExpression);
12622 }
12623 }
12624 return obj;
12625}
12626
12627var CALL = Function.prototype.call;
12628var APPLY = Function.prototype.apply;
12629var BIND = Function.prototype.bind;
12630
12631function ensureSafeFunction(obj, fullExpression) {
12632 if (obj) {
12633 if (obj.constructor === obj) {
12634 throw $parseMinErr('isecfn',
12635 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12636 fullExpression);
12637 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12638 throw $parseMinErr('isecff',
12639 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12640 fullExpression);
12641 }
12642 }
12643}
12644
12645function ensureSafeAssignContext(obj, fullExpression) {
12646 if (obj) {
12647 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12648 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12649 throw $parseMinErr('isecaf',
12650 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12651 }
12652 }
12653}
12654
12655var OPERATORS = createMap();
12656forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12657var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12658
12659
12660/////////////////////////////////////////
12661
12662
12663/**
12664 * @constructor
12665 */
12666var Lexer = function(options) {
12667 this.options = options;
12668};
12669
12670Lexer.prototype = {
12671 constructor: Lexer,
12672
12673 lex: function(text) {
12674 this.text = text;
12675 this.index = 0;
12676 this.tokens = [];
12677
12678 while (this.index < this.text.length) {
12679 var ch = this.text.charAt(this.index);
12680 if (ch === '"' || ch === "'") {
12681 this.readString(ch);
12682 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12683 this.readNumber();
12684 } else if (this.isIdent(ch)) {
12685 this.readIdent();
12686 } else if (this.is(ch, '(){}[].,;:?')) {
12687 this.tokens.push({index: this.index, text: ch});
12688 this.index++;
12689 } else if (this.isWhitespace(ch)) {
12690 this.index++;
12691 } else {
12692 var ch2 = ch + this.peek();
12693 var ch3 = ch2 + this.peek(2);
12694 var op1 = OPERATORS[ch];
12695 var op2 = OPERATORS[ch2];
12696 var op3 = OPERATORS[ch3];
12697 if (op1 || op2 || op3) {
12698 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12699 this.tokens.push({index: this.index, text: token, operator: true});
12700 this.index += token.length;
12701 } else {
12702 this.throwError('Unexpected next character ', this.index, this.index + 1);
12703 }
12704 }
12705 }
12706 return this.tokens;
12707 },
12708
12709 is: function(ch, chars) {
12710 return chars.indexOf(ch) !== -1;
12711 },
12712
12713 peek: function(i) {
12714 var num = i || 1;
12715 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12716 },
12717
12718 isNumber: function(ch) {
12719 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12720 },
12721
12722 isWhitespace: function(ch) {
12723 // IE treats non-breaking space as \u00A0
12724 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12725 ch === '\n' || ch === '\v' || ch === '\u00A0');
12726 },
12727
12728 isIdent: function(ch) {
12729 return ('a' <= ch && ch <= 'z' ||
12730 'A' <= ch && ch <= 'Z' ||
12731 '_' === ch || ch === '$');
12732 },
12733
12734 isExpOperator: function(ch) {
12735 return (ch === '-' || ch === '+' || this.isNumber(ch));
12736 },
12737
12738 throwError: function(error, start, end) {
12739 end = end || this.index;
12740 var colStr = (isDefined(start)
12741 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12742 : ' ' + end);
12743 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12744 error, colStr, this.text);
12745 },
12746
12747 readNumber: function() {
12748 var number = '';
12749 var start = this.index;
12750 while (this.index < this.text.length) {
12751 var ch = lowercase(this.text.charAt(this.index));
12752 if (ch == '.' || this.isNumber(ch)) {
12753 number += ch;
12754 } else {
12755 var peekCh = this.peek();
12756 if (ch == 'e' && this.isExpOperator(peekCh)) {
12757 number += ch;
12758 } else if (this.isExpOperator(ch) &&
12759 peekCh && this.isNumber(peekCh) &&
12760 number.charAt(number.length - 1) == 'e') {
12761 number += ch;
12762 } else if (this.isExpOperator(ch) &&
12763 (!peekCh || !this.isNumber(peekCh)) &&
12764 number.charAt(number.length - 1) == 'e') {
12765 this.throwError('Invalid exponent');
12766 } else {
12767 break;
12768 }
12769 }
12770 this.index++;
12771 }
12772 this.tokens.push({
12773 index: start,
12774 text: number,
12775 constant: true,
12776 value: Number(number)
12777 });
12778 },
12779
12780 readIdent: function() {
12781 var start = this.index;
12782 while (this.index < this.text.length) {
12783 var ch = this.text.charAt(this.index);
12784 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12785 break;
12786 }
12787 this.index++;
12788 }
12789 this.tokens.push({
12790 index: start,
12791 text: this.text.slice(start, this.index),
12792 identifier: true
12793 });
12794 },
12795
12796 readString: function(quote) {
12797 var start = this.index;
12798 this.index++;
12799 var string = '';
12800 var rawString = quote;
12801 var escape = false;
12802 while (this.index < this.text.length) {
12803 var ch = this.text.charAt(this.index);
12804 rawString += ch;
12805 if (escape) {
12806 if (ch === 'u') {
12807 var hex = this.text.substring(this.index + 1, this.index + 5);
12808 if (!hex.match(/[\da-f]{4}/i)) {
12809 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12810 }
12811 this.index += 4;
12812 string += String.fromCharCode(parseInt(hex, 16));
12813 } else {
12814 var rep = ESCAPE[ch];
12815 string = string + (rep || ch);
12816 }
12817 escape = false;
12818 } else if (ch === '\\') {
12819 escape = true;
12820 } else if (ch === quote) {
12821 this.index++;
12822 this.tokens.push({
12823 index: start,
12824 text: rawString,
12825 constant: true,
12826 value: string
12827 });
12828 return;
12829 } else {
12830 string += ch;
12831 }
12832 this.index++;
12833 }
12834 this.throwError('Unterminated quote', start);
12835 }
12836};
12837
12838var AST = function(lexer, options) {
12839 this.lexer = lexer;
12840 this.options = options;
12841};
12842
12843AST.Program = 'Program';
12844AST.ExpressionStatement = 'ExpressionStatement';
12845AST.AssignmentExpression = 'AssignmentExpression';
12846AST.ConditionalExpression = 'ConditionalExpression';
12847AST.LogicalExpression = 'LogicalExpression';
12848AST.BinaryExpression = 'BinaryExpression';
12849AST.UnaryExpression = 'UnaryExpression';
12850AST.CallExpression = 'CallExpression';
12851AST.MemberExpression = 'MemberExpression';
12852AST.Identifier = 'Identifier';
12853AST.Literal = 'Literal';
12854AST.ArrayExpression = 'ArrayExpression';
12855AST.Property = 'Property';
12856AST.ObjectExpression = 'ObjectExpression';
12857AST.ThisExpression = 'ThisExpression';
12858
12859// Internal use only
12860AST.NGValueParameter = 'NGValueParameter';
12861
12862AST.prototype = {
12863 ast: function(text) {
12864 this.text = text;
12865 this.tokens = this.lexer.lex(text);
12866
12867 var value = this.program();
12868
12869 if (this.tokens.length !== 0) {
12870 this.throwError('is an unexpected token', this.tokens[0]);
12871 }
12872
12873 return value;
12874 },
12875
12876 program: function() {
12877 var body = [];
12878 while (true) {
12879 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12880 body.push(this.expressionStatement());
12881 if (!this.expect(';')) {
12882 return { type: AST.Program, body: body};
12883 }
12884 }
12885 },
12886
12887 expressionStatement: function() {
12888 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12889 },
12890
12891 filterChain: function() {
12892 var left = this.expression();
12893 var token;
12894 while ((token = this.expect('|'))) {
12895 left = this.filter(left);
12896 }
12897 return left;
12898 },
12899
12900 expression: function() {
12901 return this.assignment();
12902 },
12903
12904 assignment: function() {
12905 var result = this.ternary();
12906 if (this.expect('=')) {
12907 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12908 }
12909 return result;
12910 },
12911
12912 ternary: function() {
12913 var test = this.logicalOR();
12914 var alternate;
12915 var consequent;
12916 if (this.expect('?')) {
12917 alternate = this.expression();
12918 if (this.consume(':')) {
12919 consequent = this.expression();
12920 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
12921 }
12922 }
12923 return test;
12924 },
12925
12926 logicalOR: function() {
12927 var left = this.logicalAND();
12928 while (this.expect('||')) {
12929 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
12930 }
12931 return left;
12932 },
12933
12934 logicalAND: function() {
12935 var left = this.equality();
12936 while (this.expect('&&')) {
12937 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
12938 }
12939 return left;
12940 },
12941
12942 equality: function() {
12943 var left = this.relational();
12944 var token;
12945 while ((token = this.expect('==','!=','===','!=='))) {
12946 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
12947 }
12948 return left;
12949 },
12950
12951 relational: function() {
12952 var left = this.additive();
12953 var token;
12954 while ((token = this.expect('<', '>', '<=', '>='))) {
12955 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
12956 }
12957 return left;
12958 },
12959
12960 additive: function() {
12961 var left = this.multiplicative();
12962 var token;
12963 while ((token = this.expect('+','-'))) {
12964 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
12965 }
12966 return left;
12967 },
12968
12969 multiplicative: function() {
12970 var left = this.unary();
12971 var token;
12972 while ((token = this.expect('*','/','%'))) {
12973 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
12974 }
12975 return left;
12976 },
12977
12978 unary: function() {
12979 var token;
12980 if ((token = this.expect('+', '-', '!'))) {
12981 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
12982 } else {
12983 return this.primary();
12984 }
12985 },
12986
12987 primary: function() {
12988 var primary;
12989 if (this.expect('(')) {
12990 primary = this.filterChain();
12991 this.consume(')');
12992 } else if (this.expect('[')) {
12993 primary = this.arrayDeclaration();
12994 } else if (this.expect('{')) {
12995 primary = this.object();
12996 } else if (this.constants.hasOwnProperty(this.peek().text)) {
12997 primary = copy(this.constants[this.consume().text]);
12998 } else if (this.peek().identifier) {
12999 primary = this.identifier();
13000 } else if (this.peek().constant) {
13001 primary = this.constant();
13002 } else {
13003 this.throwError('not a primary expression', this.peek());
13004 }
13005
13006 var next;
13007 while ((next = this.expect('(', '[', '.'))) {
13008 if (next.text === '(') {
13009 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13010 this.consume(')');
13011 } else if (next.text === '[') {
13012 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13013 this.consume(']');
13014 } else if (next.text === '.') {
13015 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13016 } else {
13017 this.throwError('IMPOSSIBLE');
13018 }
13019 }
13020 return primary;
13021 },
13022
13023 filter: function(baseExpression) {
13024 var args = [baseExpression];
13025 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13026
13027 while (this.expect(':')) {
13028 args.push(this.expression());
13029 }
13030
13031 return result;
13032 },
13033
13034 parseArguments: function() {
13035 var args = [];
13036 if (this.peekToken().text !== ')') {
13037 do {
13038 args.push(this.expression());
13039 } while (this.expect(','));
13040 }
13041 return args;
13042 },
13043
13044 identifier: function() {
13045 var token = this.consume();
13046 if (!token.identifier) {
13047 this.throwError('is not a valid identifier', token);
13048 }
13049 return { type: AST.Identifier, name: token.text };
13050 },
13051
13052 constant: function() {
13053 // TODO check that it is a constant
13054 return { type: AST.Literal, value: this.consume().value };
13055 },
13056
13057 arrayDeclaration: function() {
13058 var elements = [];
13059 if (this.peekToken().text !== ']') {
13060 do {
13061 if (this.peek(']')) {
13062 // Support trailing commas per ES5.1.
13063 break;
13064 }
13065 elements.push(this.expression());
13066 } while (this.expect(','));
13067 }
13068 this.consume(']');
13069
13070 return { type: AST.ArrayExpression, elements: elements };
13071 },
13072
13073 object: function() {
13074 var properties = [], property;
13075 if (this.peekToken().text !== '}') {
13076 do {
13077 if (this.peek('}')) {
13078 // Support trailing commas per ES5.1.
13079 break;
13080 }
13081 property = {type: AST.Property, kind: 'init'};
13082 if (this.peek().constant) {
13083 property.key = this.constant();
13084 } else if (this.peek().identifier) {
13085 property.key = this.identifier();
13086 } else {
13087 this.throwError("invalid key", this.peek());
13088 }
13089 this.consume(':');
13090 property.value = this.expression();
13091 properties.push(property);
13092 } while (this.expect(','));
13093 }
13094 this.consume('}');
13095
13096 return {type: AST.ObjectExpression, properties: properties };
13097 },
13098
13099 throwError: function(msg, token) {
13100 throw $parseMinErr('syntax',
13101 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13102 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13103 },
13104
13105 consume: function(e1) {
13106 if (this.tokens.length === 0) {
13107 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13108 }
13109
13110 var token = this.expect(e1);
13111 if (!token) {
13112 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13113 }
13114 return token;
13115 },
13116
13117 peekToken: function() {
13118 if (this.tokens.length === 0) {
13119 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13120 }
13121 return this.tokens[0];
13122 },
13123
13124 peek: function(e1, e2, e3, e4) {
13125 return this.peekAhead(0, e1, e2, e3, e4);
13126 },
13127
13128 peekAhead: function(i, e1, e2, e3, e4) {
13129 if (this.tokens.length > i) {
13130 var token = this.tokens[i];
13131 var t = token.text;
13132 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13133 (!e1 && !e2 && !e3 && !e4)) {
13134 return token;
13135 }
13136 }
13137 return false;
13138 },
13139
13140 expect: function(e1, e2, e3, e4) {
13141 var token = this.peek(e1, e2, e3, e4);
13142 if (token) {
13143 this.tokens.shift();
13144 return token;
13145 }
13146 return false;
13147 },
13148
13149
13150 /* `undefined` is not a constant, it is an identifier,
13151 * but using it as an identifier is not supported
13152 */
13153 constants: {
13154 'true': { type: AST.Literal, value: true },
13155 'false': { type: AST.Literal, value: false },
13156 'null': { type: AST.Literal, value: null },
13157 'undefined': {type: AST.Literal, value: undefined },
13158 'this': {type: AST.ThisExpression }
13159 }
13160};
13161
13162function ifDefined(v, d) {
13163 return typeof v !== 'undefined' ? v : d;
13164}
13165
13166function plusFn(l, r) {
13167 if (typeof l === 'undefined') return r;
13168 if (typeof r === 'undefined') return l;
13169 return l + r;
13170}
13171
13172function isStateless($filter, filterName) {
13173 var fn = $filter(filterName);
13174 return !fn.$stateful;
13175}
13176
13177function findConstantAndWatchExpressions(ast, $filter) {
13178 var allConstants;
13179 var argsToWatch;
13180 switch (ast.type) {
13181 case AST.Program:
13182 allConstants = true;
13183 forEach(ast.body, function(expr) {
13184 findConstantAndWatchExpressions(expr.expression, $filter);
13185 allConstants = allConstants && expr.expression.constant;
13186 });
13187 ast.constant = allConstants;
13188 break;
13189 case AST.Literal:
13190 ast.constant = true;
13191 ast.toWatch = [];
13192 break;
13193 case AST.UnaryExpression:
13194 findConstantAndWatchExpressions(ast.argument, $filter);
13195 ast.constant = ast.argument.constant;
13196 ast.toWatch = ast.argument.toWatch;
13197 break;
13198 case AST.BinaryExpression:
13199 findConstantAndWatchExpressions(ast.left, $filter);
13200 findConstantAndWatchExpressions(ast.right, $filter);
13201 ast.constant = ast.left.constant && ast.right.constant;
13202 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13203 break;
13204 case AST.LogicalExpression:
13205 findConstantAndWatchExpressions(ast.left, $filter);
13206 findConstantAndWatchExpressions(ast.right, $filter);
13207 ast.constant = ast.left.constant && ast.right.constant;
13208 ast.toWatch = ast.constant ? [] : [ast];
13209 break;
13210 case AST.ConditionalExpression:
13211 findConstantAndWatchExpressions(ast.test, $filter);
13212 findConstantAndWatchExpressions(ast.alternate, $filter);
13213 findConstantAndWatchExpressions(ast.consequent, $filter);
13214 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13215 ast.toWatch = ast.constant ? [] : [ast];
13216 break;
13217 case AST.Identifier:
13218 ast.constant = false;
13219 ast.toWatch = [ast];
13220 break;
13221 case AST.MemberExpression:
13222 findConstantAndWatchExpressions(ast.object, $filter);
13223 if (ast.computed) {
13224 findConstantAndWatchExpressions(ast.property, $filter);
13225 }
13226 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13227 ast.toWatch = [ast];
13228 break;
13229 case AST.CallExpression:
13230 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13231 argsToWatch = [];
13232 forEach(ast.arguments, function(expr) {
13233 findConstantAndWatchExpressions(expr, $filter);
13234 allConstants = allConstants && expr.constant;
13235 if (!expr.constant) {
13236 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13237 }
13238 });
13239 ast.constant = allConstants;
13240 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13241 break;
13242 case AST.AssignmentExpression:
13243 findConstantAndWatchExpressions(ast.left, $filter);
13244 findConstantAndWatchExpressions(ast.right, $filter);
13245 ast.constant = ast.left.constant && ast.right.constant;
13246 ast.toWatch = [ast];
13247 break;
13248 case AST.ArrayExpression:
13249 allConstants = true;
13250 argsToWatch = [];
13251 forEach(ast.elements, function(expr) {
13252 findConstantAndWatchExpressions(expr, $filter);
13253 allConstants = allConstants && expr.constant;
13254 if (!expr.constant) {
13255 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13256 }
13257 });
13258 ast.constant = allConstants;
13259 ast.toWatch = argsToWatch;
13260 break;
13261 case AST.ObjectExpression:
13262 allConstants = true;
13263 argsToWatch = [];
13264 forEach(ast.properties, function(property) {
13265 findConstantAndWatchExpressions(property.value, $filter);
13266 allConstants = allConstants && property.value.constant;
13267 if (!property.value.constant) {
13268 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13269 }
13270 });
13271 ast.constant = allConstants;
13272 ast.toWatch = argsToWatch;
13273 break;
13274 case AST.ThisExpression:
13275 ast.constant = false;
13276 ast.toWatch = [];
13277 break;
13278 }
13279}
13280
13281function getInputs(body) {
13282 if (body.length != 1) return;
13283 var lastExpression = body[0].expression;
13284 var candidate = lastExpression.toWatch;
13285 if (candidate.length !== 1) return candidate;
13286 return candidate[0] !== lastExpression ? candidate : undefined;
13287}
13288
13289function isAssignable(ast) {
13290 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13291}
13292
13293function assignableAST(ast) {
13294 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13295 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13296 }
13297}
13298
13299function isLiteral(ast) {
13300 return ast.body.length === 0 ||
13301 ast.body.length === 1 && (
13302 ast.body[0].expression.type === AST.Literal ||
13303 ast.body[0].expression.type === AST.ArrayExpression ||
13304 ast.body[0].expression.type === AST.ObjectExpression);
13305}
13306
13307function isConstant(ast) {
13308 return ast.constant;
13309}
13310
13311function ASTCompiler(astBuilder, $filter) {
13312 this.astBuilder = astBuilder;
13313 this.$filter = $filter;
13314}
13315
13316ASTCompiler.prototype = {
13317 compile: function(expression, expensiveChecks) {
13318 var self = this;
13319 var ast = this.astBuilder.ast(expression);
13320 this.state = {
13321 nextId: 0,
13322 filters: {},
13323 expensiveChecks: expensiveChecks,
13324 fn: {vars: [], body: [], own: {}},
13325 assign: {vars: [], body: [], own: {}},
13326 inputs: []
13327 };
13328 findConstantAndWatchExpressions(ast, self.$filter);
13329 var extra = '';
13330 var assignable;
13331 this.stage = 'assign';
13332 if ((assignable = assignableAST(ast))) {
13333 this.state.computing = 'assign';
13334 var result = this.nextId();
13335 this.recurse(assignable, result);
13336 this.return_(result);
13337 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13338 }
13339 var toWatch = getInputs(ast.body);
13340 self.stage = 'inputs';
13341 forEach(toWatch, function(watch, key) {
13342 var fnKey = 'fn' + key;
13343 self.state[fnKey] = {vars: [], body: [], own: {}};
13344 self.state.computing = fnKey;
13345 var intoId = self.nextId();
13346 self.recurse(watch, intoId);
13347 self.return_(intoId);
13348 self.state.inputs.push(fnKey);
13349 watch.watchId = key;
13350 });
13351 this.state.computing = 'fn';
13352 this.stage = 'main';
13353 this.recurse(ast);
13354 var fnString =
13355 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13356 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13357 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13358 this.filterPrefix() +
13359 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13360 extra +
13361 this.watchFns() +
13362 'return fn;';
13363
13364 /* jshint -W054 */
13365 var fn = (new Function('$filter',
13366 'ensureSafeMemberName',
13367 'ensureSafeObject',
13368 'ensureSafeFunction',
13369 'getStringValue',
13370 'ensureSafeAssignContext',
13371 'ifDefined',
13372 'plus',
13373 'text',
13374 fnString))(
13375 this.$filter,
13376 ensureSafeMemberName,
13377 ensureSafeObject,
13378 ensureSafeFunction,
13379 getStringValue,
13380 ensureSafeAssignContext,
13381 ifDefined,
13382 plusFn,
13383 expression);
13384 /* jshint +W054 */
13385 this.state = this.stage = undefined;
13386 fn.literal = isLiteral(ast);
13387 fn.constant = isConstant(ast);
13388 return fn;
13389 },
13390
13391 USE: 'use',
13392
13393 STRICT: 'strict',
13394
13395 watchFns: function() {
13396 var result = [];
13397 var fns = this.state.inputs;
13398 var self = this;
13399 forEach(fns, function(name) {
13400 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13401 });
13402 if (fns.length) {
13403 result.push('fn.inputs=[' + fns.join(',') + '];');
13404 }
13405 return result.join('');
13406 },
13407
13408 generateFunction: function(name, params) {
13409 return 'function(' + params + '){' +
13410 this.varsPrefix(name) +
13411 this.body(name) +
13412 '};';
13413 },
13414
13415 filterPrefix: function() {
13416 var parts = [];
13417 var self = this;
13418 forEach(this.state.filters, function(id, filter) {
13419 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13420 });
13421 if (parts.length) return 'var ' + parts.join(',') + ';';
13422 return '';
13423 },
13424
13425 varsPrefix: function(section) {
13426 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13427 },
13428
13429 body: function(section) {
13430 return this.state[section].body.join('');
13431 },
13432
13433 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13434 var left, right, self = this, args, expression;
13435 recursionFn = recursionFn || noop;
13436 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13437 intoId = intoId || this.nextId();
13438 this.if_('i',
13439 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13440 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13441 );
13442 return;
13443 }
13444 switch (ast.type) {
13445 case AST.Program:
13446 forEach(ast.body, function(expression, pos) {
13447 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13448 if (pos !== ast.body.length - 1) {
13449 self.current().body.push(right, ';');
13450 } else {
13451 self.return_(right);
13452 }
13453 });
13454 break;
13455 case AST.Literal:
13456 expression = this.escape(ast.value);
13457 this.assign(intoId, expression);
13458 recursionFn(expression);
13459 break;
13460 case AST.UnaryExpression:
13461 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13462 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13463 this.assign(intoId, expression);
13464 recursionFn(expression);
13465 break;
13466 case AST.BinaryExpression:
13467 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13468 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13469 if (ast.operator === '+') {
13470 expression = this.plus(left, right);
13471 } else if (ast.operator === '-') {
13472 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13473 } else {
13474 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13475 }
13476 this.assign(intoId, expression);
13477 recursionFn(expression);
13478 break;
13479 case AST.LogicalExpression:
13480 intoId = intoId || this.nextId();
13481 self.recurse(ast.left, intoId);
13482 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13483 recursionFn(intoId);
13484 break;
13485 case AST.ConditionalExpression:
13486 intoId = intoId || this.nextId();
13487 self.recurse(ast.test, intoId);
13488 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13489 recursionFn(intoId);
13490 break;
13491 case AST.Identifier:
13492 intoId = intoId || this.nextId();
13493 if (nameId) {
13494 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13495 nameId.computed = false;
13496 nameId.name = ast.name;
13497 }
13498 ensureSafeMemberName(ast.name);
13499 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13500 function() {
13501 self.if_(self.stage === 'inputs' || 's', function() {
13502 if (create && create !== 1) {
13503 self.if_(
13504 self.not(self.nonComputedMember('s', ast.name)),
13505 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13506 }
13507 self.assign(intoId, self.nonComputedMember('s', ast.name));
13508 });
13509 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13510 );
13511 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13512 self.addEnsureSafeObject(intoId);
13513 }
13514 recursionFn(intoId);
13515 break;
13516 case AST.MemberExpression:
13517 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13518 intoId = intoId || this.nextId();
13519 self.recurse(ast.object, left, undefined, function() {
13520 self.if_(self.notNull(left), function() {
13521 if (ast.computed) {
13522 right = self.nextId();
13523 self.recurse(ast.property, right);
13524 self.getStringValue(right);
13525 self.addEnsureSafeMemberName(right);
13526 if (create && create !== 1) {
13527 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13528 }
13529 expression = self.ensureSafeObject(self.computedMember(left, right));
13530 self.assign(intoId, expression);
13531 if (nameId) {
13532 nameId.computed = true;
13533 nameId.name = right;
13534 }
13535 } else {
13536 ensureSafeMemberName(ast.property.name);
13537 if (create && create !== 1) {
13538 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13539 }
13540 expression = self.nonComputedMember(left, ast.property.name);
13541 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13542 expression = self.ensureSafeObject(expression);
13543 }
13544 self.assign(intoId, expression);
13545 if (nameId) {
13546 nameId.computed = false;
13547 nameId.name = ast.property.name;
13548 }
13549 }
13550 }, function() {
13551 self.assign(intoId, 'undefined');
13552 });
13553 recursionFn(intoId);
13554 }, !!create);
13555 break;
13556 case AST.CallExpression:
13557 intoId = intoId || this.nextId();
13558 if (ast.filter) {
13559 right = self.filter(ast.callee.name);
13560 args = [];
13561 forEach(ast.arguments, function(expr) {
13562 var argument = self.nextId();
13563 self.recurse(expr, argument);
13564 args.push(argument);
13565 });
13566 expression = right + '(' + args.join(',') + ')';
13567 self.assign(intoId, expression);
13568 recursionFn(intoId);
13569 } else {
13570 right = self.nextId();
13571 left = {};
13572 args = [];
13573 self.recurse(ast.callee, right, left, function() {
13574 self.if_(self.notNull(right), function() {
13575 self.addEnsureSafeFunction(right);
13576 forEach(ast.arguments, function(expr) {
13577 self.recurse(expr, self.nextId(), undefined, function(argument) {
13578 args.push(self.ensureSafeObject(argument));
13579 });
13580 });
13581 if (left.name) {
13582 if (!self.state.expensiveChecks) {
13583 self.addEnsureSafeObject(left.context);
13584 }
13585 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13586 } else {
13587 expression = right + '(' + args.join(',') + ')';
13588 }
13589 expression = self.ensureSafeObject(expression);
13590 self.assign(intoId, expression);
13591 }, function() {
13592 self.assign(intoId, 'undefined');
13593 });
13594 recursionFn(intoId);
13595 });
13596 }
13597 break;
13598 case AST.AssignmentExpression:
13599 right = this.nextId();
13600 left = {};
13601 if (!isAssignable(ast.left)) {
13602 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13603 }
13604 this.recurse(ast.left, undefined, left, function() {
13605 self.if_(self.notNull(left.context), function() {
13606 self.recurse(ast.right, right);
13607 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13608 self.addEnsureSafeAssignContext(left.context);
13609 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13610 self.assign(intoId, expression);
13611 recursionFn(intoId || expression);
13612 });
13613 }, 1);
13614 break;
13615 case AST.ArrayExpression:
13616 args = [];
13617 forEach(ast.elements, function(expr) {
13618 self.recurse(expr, self.nextId(), undefined, function(argument) {
13619 args.push(argument);
13620 });
13621 });
13622 expression = '[' + args.join(',') + ']';
13623 this.assign(intoId, expression);
13624 recursionFn(expression);
13625 break;
13626 case AST.ObjectExpression:
13627 args = [];
13628 forEach(ast.properties, function(property) {
13629 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13630 args.push(self.escape(
13631 property.key.type === AST.Identifier ? property.key.name :
13632 ('' + property.key.value)) +
13633 ':' + expr);
13634 });
13635 });
13636 expression = '{' + args.join(',') + '}';
13637 this.assign(intoId, expression);
13638 recursionFn(expression);
13639 break;
13640 case AST.ThisExpression:
13641 this.assign(intoId, 's');
13642 recursionFn('s');
13643 break;
13644 case AST.NGValueParameter:
13645 this.assign(intoId, 'v');
13646 recursionFn('v');
13647 break;
13648 }
13649 },
13650
13651 getHasOwnProperty: function(element, property) {
13652 var key = element + '.' + property;
13653 var own = this.current().own;
13654 if (!own.hasOwnProperty(key)) {
13655 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13656 }
13657 return own[key];
13658 },
13659
13660 assign: function(id, value) {
13661 if (!id) return;
13662 this.current().body.push(id, '=', value, ';');
13663 return id;
13664 },
13665
13666 filter: function(filterName) {
13667 if (!this.state.filters.hasOwnProperty(filterName)) {
13668 this.state.filters[filterName] = this.nextId(true);
13669 }
13670 return this.state.filters[filterName];
13671 },
13672
13673 ifDefined: function(id, defaultValue) {
13674 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13675 },
13676
13677 plus: function(left, right) {
13678 return 'plus(' + left + ',' + right + ')';
13679 },
13680
13681 return_: function(id) {
13682 this.current().body.push('return ', id, ';');
13683 },
13684
13685 if_: function(test, alternate, consequent) {
13686 if (test === true) {
13687 alternate();
13688 } else {
13689 var body = this.current().body;
13690 body.push('if(', test, '){');
13691 alternate();
13692 body.push('}');
13693 if (consequent) {
13694 body.push('else{');
13695 consequent();
13696 body.push('}');
13697 }
13698 }
13699 },
13700
13701 not: function(expression) {
13702 return '!(' + expression + ')';
13703 },
13704
13705 notNull: function(expression) {
13706 return expression + '!=null';
13707 },
13708
13709 nonComputedMember: function(left, right) {
13710 return left + '.' + right;
13711 },
13712
13713 computedMember: function(left, right) {
13714 return left + '[' + right + ']';
13715 },
13716
13717 member: function(left, right, computed) {
13718 if (computed) return this.computedMember(left, right);
13719 return this.nonComputedMember(left, right);
13720 },
13721
13722 addEnsureSafeObject: function(item) {
13723 this.current().body.push(this.ensureSafeObject(item), ';');
13724 },
13725
13726 addEnsureSafeMemberName: function(item) {
13727 this.current().body.push(this.ensureSafeMemberName(item), ';');
13728 },
13729
13730 addEnsureSafeFunction: function(item) {
13731 this.current().body.push(this.ensureSafeFunction(item), ';');
13732 },
13733
13734 addEnsureSafeAssignContext: function(item) {
13735 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13736 },
13737
13738 ensureSafeObject: function(item) {
13739 return 'ensureSafeObject(' + item + ',text)';
13740 },
13741
13742 ensureSafeMemberName: function(item) {
13743 return 'ensureSafeMemberName(' + item + ',text)';
13744 },
13745
13746 ensureSafeFunction: function(item) {
13747 return 'ensureSafeFunction(' + item + ',text)';
13748 },
13749
13750 getStringValue: function(item) {
13751 this.assign(item, 'getStringValue(' + item + ',text)');
13752 },
13753
13754 ensureSafeAssignContext: function(item) {
13755 return 'ensureSafeAssignContext(' + item + ',text)';
13756 },
13757
13758 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13759 var self = this;
13760 return function() {
13761 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13762 };
13763 },
13764
13765 lazyAssign: function(id, value) {
13766 var self = this;
13767 return function() {
13768 self.assign(id, value);
13769 };
13770 },
13771
13772 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13773
13774 stringEscapeFn: function(c) {
13775 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13776 },
13777
13778 escape: function(value) {
13779 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13780 if (isNumber(value)) return value.toString();
13781 if (value === true) return 'true';
13782 if (value === false) return 'false';
13783 if (value === null) return 'null';
13784 if (typeof value === 'undefined') return 'undefined';
13785
13786 throw $parseMinErr('esc', 'IMPOSSIBLE');
13787 },
13788
13789 nextId: function(skip, init) {
13790 var id = 'v' + (this.state.nextId++);
13791 if (!skip) {
13792 this.current().vars.push(id + (init ? '=' + init : ''));
13793 }
13794 return id;
13795 },
13796
13797 current: function() {
13798 return this.state[this.state.computing];
13799 }
13800};
13801
13802
13803function ASTInterpreter(astBuilder, $filter) {
13804 this.astBuilder = astBuilder;
13805 this.$filter = $filter;
13806}
13807
13808ASTInterpreter.prototype = {
13809 compile: function(expression, expensiveChecks) {
13810 var self = this;
13811 var ast = this.astBuilder.ast(expression);
13812 this.expression = expression;
13813 this.expensiveChecks = expensiveChecks;
13814 findConstantAndWatchExpressions(ast, self.$filter);
13815 var assignable;
13816 var assign;
13817 if ((assignable = assignableAST(ast))) {
13818 assign = this.recurse(assignable);
13819 }
13820 var toWatch = getInputs(ast.body);
13821 var inputs;
13822 if (toWatch) {
13823 inputs = [];
13824 forEach(toWatch, function(watch, key) {
13825 var input = self.recurse(watch);
13826 watch.input = input;
13827 inputs.push(input);
13828 watch.watchId = key;
13829 });
13830 }
13831 var expressions = [];
13832 forEach(ast.body, function(expression) {
13833 expressions.push(self.recurse(expression.expression));
13834 });
13835 var fn = ast.body.length === 0 ? function() {} :
13836 ast.body.length === 1 ? expressions[0] :
13837 function(scope, locals) {
13838 var lastValue;
13839 forEach(expressions, function(exp) {
13840 lastValue = exp(scope, locals);
13841 });
13842 return lastValue;
13843 };
13844 if (assign) {
13845 fn.assign = function(scope, value, locals) {
13846 return assign(scope, locals, value);
13847 };
13848 }
13849 if (inputs) {
13850 fn.inputs = inputs;
13851 }
13852 fn.literal = isLiteral(ast);
13853 fn.constant = isConstant(ast);
13854 return fn;
13855 },
13856
13857 recurse: function(ast, context, create) {
13858 var left, right, self = this, args, expression;
13859 if (ast.input) {
13860 return this.inputs(ast.input, ast.watchId);
13861 }
13862 switch (ast.type) {
13863 case AST.Literal:
13864 return this.value(ast.value, context);
13865 case AST.UnaryExpression:
13866 right = this.recurse(ast.argument);
13867 return this['unary' + ast.operator](right, context);
13868 case AST.BinaryExpression:
13869 left = this.recurse(ast.left);
13870 right = this.recurse(ast.right);
13871 return this['binary' + ast.operator](left, right, context);
13872 case AST.LogicalExpression:
13873 left = this.recurse(ast.left);
13874 right = this.recurse(ast.right);
13875 return this['binary' + ast.operator](left, right, context);
13876 case AST.ConditionalExpression:
13877 return this['ternary?:'](
13878 this.recurse(ast.test),
13879 this.recurse(ast.alternate),
13880 this.recurse(ast.consequent),
13881 context
13882 );
13883 case AST.Identifier:
13884 ensureSafeMemberName(ast.name, self.expression);
13885 return self.identifier(ast.name,
13886 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13887 context, create, self.expression);
13888 case AST.MemberExpression:
13889 left = this.recurse(ast.object, false, !!create);
13890 if (!ast.computed) {
13891 ensureSafeMemberName(ast.property.name, self.expression);
13892 right = ast.property.name;
13893 }
13894 if (ast.computed) right = this.recurse(ast.property);
13895 return ast.computed ?
13896 this.computedMember(left, right, context, create, self.expression) :
13897 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13898 case AST.CallExpression:
13899 args = [];
13900 forEach(ast.arguments, function(expr) {
13901 args.push(self.recurse(expr));
13902 });
13903 if (ast.filter) right = this.$filter(ast.callee.name);
13904 if (!ast.filter) right = this.recurse(ast.callee, true);
13905 return ast.filter ?
13906 function(scope, locals, assign, inputs) {
13907 var values = [];
13908 for (var i = 0; i < args.length; ++i) {
13909 values.push(args[i](scope, locals, assign, inputs));
13910 }
13911 var value = right.apply(undefined, values, inputs);
13912 return context ? {context: undefined, name: undefined, value: value} : value;
13913 } :
13914 function(scope, locals, assign, inputs) {
13915 var rhs = right(scope, locals, assign, inputs);
13916 var value;
13917 if (rhs.value != null) {
13918 ensureSafeObject(rhs.context, self.expression);
13919 ensureSafeFunction(rhs.value, self.expression);
13920 var values = [];
13921 for (var i = 0; i < args.length; ++i) {
13922 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
13923 }
13924 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
13925 }
13926 return context ? {value: value} : value;
13927 };
13928 case AST.AssignmentExpression:
13929 left = this.recurse(ast.left, true, 1);
13930 right = this.recurse(ast.right);
13931 return function(scope, locals, assign, inputs) {
13932 var lhs = left(scope, locals, assign, inputs);
13933 var rhs = right(scope, locals, assign, inputs);
13934 ensureSafeObject(lhs.value, self.expression);
13935 ensureSafeAssignContext(lhs.context);
13936 lhs.context[lhs.name] = rhs;
13937 return context ? {value: rhs} : rhs;
13938 };
13939 case AST.ArrayExpression:
13940 args = [];
13941 forEach(ast.elements, function(expr) {
13942 args.push(self.recurse(expr));
13943 });
13944 return function(scope, locals, assign, inputs) {
13945 var value = [];
13946 for (var i = 0; i < args.length; ++i) {
13947 value.push(args[i](scope, locals, assign, inputs));
13948 }
13949 return context ? {value: value} : value;
13950 };
13951 case AST.ObjectExpression:
13952 args = [];
13953 forEach(ast.properties, function(property) {
13954 args.push({key: property.key.type === AST.Identifier ?
13955 property.key.name :
13956 ('' + property.key.value),
13957 value: self.recurse(property.value)
13958 });
13959 });
13960 return function(scope, locals, assign, inputs) {
13961 var value = {};
13962 for (var i = 0; i < args.length; ++i) {
13963 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
13964 }
13965 return context ? {value: value} : value;
13966 };
13967 case AST.ThisExpression:
13968 return function(scope) {
13969 return context ? {value: scope} : scope;
13970 };
13971 case AST.NGValueParameter:
13972 return function(scope, locals, assign, inputs) {
13973 return context ? {value: assign} : assign;
13974 };
13975 }
13976 },
13977
13978 'unary+': function(argument, context) {
13979 return function(scope, locals, assign, inputs) {
13980 var arg = argument(scope, locals, assign, inputs);
13981 if (isDefined(arg)) {
13982 arg = +arg;
13983 } else {
13984 arg = 0;
13985 }
13986 return context ? {value: arg} : arg;
13987 };
13988 },
13989 'unary-': function(argument, context) {
13990 return function(scope, locals, assign, inputs) {
13991 var arg = argument(scope, locals, assign, inputs);
13992 if (isDefined(arg)) {
13993 arg = -arg;
13994 } else {
13995 arg = 0;
13996 }
13997 return context ? {value: arg} : arg;
13998 };
13999 },
14000 'unary!': function(argument, context) {
14001 return function(scope, locals, assign, inputs) {
14002 var arg = !argument(scope, locals, assign, inputs);
14003 return context ? {value: arg} : arg;
14004 };
14005 },
14006 'binary+': function(left, right, context) {
14007 return function(scope, locals, assign, inputs) {
14008 var lhs = left(scope, locals, assign, inputs);
14009 var rhs = right(scope, locals, assign, inputs);
14010 var arg = plusFn(lhs, rhs);
14011 return context ? {value: arg} : arg;
14012 };
14013 },
14014 'binary-': function(left, right, context) {
14015 return function(scope, locals, assign, inputs) {
14016 var lhs = left(scope, locals, assign, inputs);
14017 var rhs = right(scope, locals, assign, inputs);
14018 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14019 return context ? {value: arg} : arg;
14020 };
14021 },
14022 'binary*': function(left, right, context) {
14023 return function(scope, locals, assign, inputs) {
14024 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14025 return context ? {value: arg} : arg;
14026 };
14027 },
14028 'binary/': function(left, right, context) {
14029 return function(scope, locals, assign, inputs) {
14030 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14031 return context ? {value: arg} : arg;
14032 };
14033 },
14034 'binary%': function(left, right, context) {
14035 return function(scope, locals, assign, inputs) {
14036 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14037 return context ? {value: arg} : arg;
14038 };
14039 },
14040 'binary===': function(left, right, context) {
14041 return function(scope, locals, assign, inputs) {
14042 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14043 return context ? {value: arg} : arg;
14044 };
14045 },
14046 'binary!==': function(left, right, context) {
14047 return function(scope, locals, assign, inputs) {
14048 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14049 return context ? {value: arg} : arg;
14050 };
14051 },
14052 'binary==': function(left, right, context) {
14053 return function(scope, locals, assign, inputs) {
14054 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14055 return context ? {value: arg} : arg;
14056 };
14057 },
14058 'binary!=': function(left, right, context) {
14059 return function(scope, locals, assign, inputs) {
14060 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14061 return context ? {value: arg} : arg;
14062 };
14063 },
14064 'binary<': function(left, right, context) {
14065 return function(scope, locals, assign, inputs) {
14066 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14067 return context ? {value: arg} : arg;
14068 };
14069 },
14070 'binary>': function(left, right, context) {
14071 return function(scope, locals, assign, inputs) {
14072 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14073 return context ? {value: arg} : arg;
14074 };
14075 },
14076 'binary<=': function(left, right, context) {
14077 return function(scope, locals, assign, inputs) {
14078 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14079 return context ? {value: arg} : arg;
14080 };
14081 },
14082 'binary>=': function(left, right, context) {
14083 return function(scope, locals, assign, inputs) {
14084 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14085 return context ? {value: arg} : arg;
14086 };
14087 },
14088 'binary&&': function(left, right, context) {
14089 return function(scope, locals, assign, inputs) {
14090 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14091 return context ? {value: arg} : arg;
14092 };
14093 },
14094 'binary||': function(left, right, context) {
14095 return function(scope, locals, assign, inputs) {
14096 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14097 return context ? {value: arg} : arg;
14098 };
14099 },
14100 'ternary?:': function(test, alternate, consequent, context) {
14101 return function(scope, locals, assign, inputs) {
14102 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14103 return context ? {value: arg} : arg;
14104 };
14105 },
14106 value: function(value, context) {
14107 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14108 },
14109 identifier: function(name, expensiveChecks, context, create, expression) {
14110 return function(scope, locals, assign, inputs) {
14111 var base = locals && (name in locals) ? locals : scope;
14112 if (create && create !== 1 && base && !(base[name])) {
14113 base[name] = {};
14114 }
14115 var value = base ? base[name] : undefined;
14116 if (expensiveChecks) {
14117 ensureSafeObject(value, expression);
14118 }
14119 if (context) {
14120 return {context: base, name: name, value: value};
14121 } else {
14122 return value;
14123 }
14124 };
14125 },
14126 computedMember: function(left, right, context, create, expression) {
14127 return function(scope, locals, assign, inputs) {
14128 var lhs = left(scope, locals, assign, inputs);
14129 var rhs;
14130 var value;
14131 if (lhs != null) {
14132 rhs = right(scope, locals, assign, inputs);
14133 rhs = getStringValue(rhs);
14134 ensureSafeMemberName(rhs, expression);
14135 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14136 lhs[rhs] = {};
14137 }
14138 value = lhs[rhs];
14139 ensureSafeObject(value, expression);
14140 }
14141 if (context) {
14142 return {context: lhs, name: rhs, value: value};
14143 } else {
14144 return value;
14145 }
14146 };
14147 },
14148 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14149 return function(scope, locals, assign, inputs) {
14150 var lhs = left(scope, locals, assign, inputs);
14151 if (create && create !== 1 && lhs && !(lhs[right])) {
14152 lhs[right] = {};
14153 }
14154 var value = lhs != null ? lhs[right] : undefined;
14155 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14156 ensureSafeObject(value, expression);
14157 }
14158 if (context) {
14159 return {context: lhs, name: right, value: value};
14160 } else {
14161 return value;
14162 }
14163 };
14164 },
14165 inputs: function(input, watchId) {
14166 return function(scope, value, locals, inputs) {
14167 if (inputs) return inputs[watchId];
14168 return input(scope, value, locals);
14169 };
14170 }
14171};
14172
14173/**
14174 * @constructor
14175 */
14176var Parser = function(lexer, $filter, options) {
14177 this.lexer = lexer;
14178 this.$filter = $filter;
14179 this.options = options;
14180 this.ast = new AST(this.lexer);
14181 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14182 new ASTCompiler(this.ast, $filter);
14183};
14184
14185Parser.prototype = {
14186 constructor: Parser,
14187
14188 parse: function(text) {
14189 return this.astCompiler.compile(text, this.options.expensiveChecks);
14190 }
14191};
14192
14193var getterFnCacheDefault = createMap();
14194var getterFnCacheExpensive = createMap();
14195
14196function isPossiblyDangerousMemberName(name) {
14197 return name == 'constructor';
14198}
14199
14200var objectValueOf = Object.prototype.valueOf;
14201
14202function getValueOf(value) {
14203 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14204}
14205
14206///////////////////////////////////
14207
14208/**
14209 * @ngdoc service
14210 * @name $parse
14211 * @kind function
14212 *
14213 * @description
14214 *
14215 * Converts Angular {@link guide/expression expression} into a function.
14216 *
14217 * ```js
14218 * var getter = $parse('user.name');
14219 * var setter = getter.assign;
14220 * var context = {user:{name:'angular'}};
14221 * var locals = {user:{name:'local'}};
14222 *
14223 * expect(getter(context)).toEqual('angular');
14224 * setter(context, 'newValue');
14225 * expect(context.user.name).toEqual('newValue');
14226 * expect(getter(context, locals)).toEqual('local');
14227 * ```
14228 *
14229 *
14230 * @param {string} expression String expression to compile.
14231 * @returns {function(context, locals)} a function which represents the compiled expression:
14232 *
14233 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14234 * are evaluated against (typically a scope object).
14235 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14236 * `context`.
14237 *
14238 * The returned function also has the following properties:
14239 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14240 * literal.
14241 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14242 * constant literals.
14243 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14244 * set to a function to change its value on the given context.
14245 *
14246 */
14247
14248
14249/**
14250 * @ngdoc provider
14251 * @name $parseProvider
14252 *
14253 * @description
14254 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14255 * service.
14256 */
14257function $ParseProvider() {
14258 var cacheDefault = createMap();
14259 var cacheExpensive = createMap();
14260
14261 this.$get = ['$filter', function($filter) {
14262 var noUnsafeEval = csp().noUnsafeEval;
14263 var $parseOptions = {
14264 csp: noUnsafeEval,
14265 expensiveChecks: false
14266 },
14267 $parseOptionsExpensive = {
14268 csp: noUnsafeEval,
14269 expensiveChecks: true
14270 };
14271
14272 return function $parse(exp, interceptorFn, expensiveChecks) {
14273 var parsedExpression, oneTime, cacheKey;
14274
14275 switch (typeof exp) {
14276 case 'string':
14277 exp = exp.trim();
14278 cacheKey = exp;
14279
14280 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14281 parsedExpression = cache[cacheKey];
14282
14283 if (!parsedExpression) {
14284 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14285 oneTime = true;
14286 exp = exp.substring(2);
14287 }
14288 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14289 var lexer = new Lexer(parseOptions);
14290 var parser = new Parser(lexer, $filter, parseOptions);
14291 parsedExpression = parser.parse(exp);
14292 if (parsedExpression.constant) {
14293 parsedExpression.$$watchDelegate = constantWatchDelegate;
14294 } else if (oneTime) {
14295 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14296 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14297 } else if (parsedExpression.inputs) {
14298 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14299 }
14300 cache[cacheKey] = parsedExpression;
14301 }
14302 return addInterceptor(parsedExpression, interceptorFn);
14303
14304 case 'function':
14305 return addInterceptor(exp, interceptorFn);
14306
14307 default:
14308 return noop;
14309 }
14310 };
14311
14312 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14313
14314 if (newValue == null || oldValueOfValue == null) { // null/undefined
14315 return newValue === oldValueOfValue;
14316 }
14317
14318 if (typeof newValue === 'object') {
14319
14320 // attempt to convert the value to a primitive type
14321 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14322 // be cheaply dirty-checked
14323 newValue = getValueOf(newValue);
14324
14325 if (typeof newValue === 'object') {
14326 // objects/arrays are not supported - deep-watching them would be too expensive
14327 return false;
14328 }
14329
14330 // fall-through to the primitive equality check
14331 }
14332
14333 //Primitive or NaN
14334 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14335 }
14336
14337 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14338 var inputExpressions = parsedExpression.inputs;
14339 var lastResult;
14340
14341 if (inputExpressions.length === 1) {
14342 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14343 inputExpressions = inputExpressions[0];
14344 return scope.$watch(function expressionInputWatch(scope) {
14345 var newInputValue = inputExpressions(scope);
14346 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14347 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14348 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14349 }
14350 return lastResult;
14351 }, listener, objectEquality, prettyPrintExpression);
14352 }
14353
14354 var oldInputValueOfValues = [];
14355 var oldInputValues = [];
14356 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14357 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14358 oldInputValues[i] = null;
14359 }
14360
14361 return scope.$watch(function expressionInputsWatch(scope) {
14362 var changed = false;
14363
14364 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14365 var newInputValue = inputExpressions[i](scope);
14366 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14367 oldInputValues[i] = newInputValue;
14368 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14369 }
14370 }
14371
14372 if (changed) {
14373 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14374 }
14375
14376 return lastResult;
14377 }, listener, objectEquality, prettyPrintExpression);
14378 }
14379
14380 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14381 var unwatch, lastValue;
14382 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14383 return parsedExpression(scope);
14384 }, function oneTimeListener(value, old, scope) {
14385 lastValue = value;
14386 if (isFunction(listener)) {
14387 listener.apply(this, arguments);
14388 }
14389 if (isDefined(value)) {
14390 scope.$$postDigest(function() {
14391 if (isDefined(lastValue)) {
14392 unwatch();
14393 }
14394 });
14395 }
14396 }, objectEquality);
14397 }
14398
14399 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14400 var unwatch, lastValue;
14401 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14402 return parsedExpression(scope);
14403 }, function oneTimeListener(value, old, scope) {
14404 lastValue = value;
14405 if (isFunction(listener)) {
14406 listener.call(this, value, old, scope);
14407 }
14408 if (isAllDefined(value)) {
14409 scope.$$postDigest(function() {
14410 if (isAllDefined(lastValue)) unwatch();
14411 });
14412 }
14413 }, objectEquality);
14414
14415 function isAllDefined(value) {
14416 var allDefined = true;
14417 forEach(value, function(val) {
14418 if (!isDefined(val)) allDefined = false;
14419 });
14420 return allDefined;
14421 }
14422 }
14423
14424 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14425 var unwatch;
14426 return unwatch = scope.$watch(function constantWatch(scope) {
14427 return parsedExpression(scope);
14428 }, function constantListener(value, old, scope) {
14429 if (isFunction(listener)) {
14430 listener.apply(this, arguments);
14431 }
14432 unwatch();
14433 }, objectEquality);
14434 }
14435
14436 function addInterceptor(parsedExpression, interceptorFn) {
14437 if (!interceptorFn) return parsedExpression;
14438 var watchDelegate = parsedExpression.$$watchDelegate;
14439 var useInputs = false;
14440
14441 var regularWatch =
14442 watchDelegate !== oneTimeLiteralWatchDelegate &&
14443 watchDelegate !== oneTimeWatchDelegate;
14444
14445 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14446 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14447 return interceptorFn(value, scope, locals);
14448 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14449 var value = parsedExpression(scope, locals, assign, inputs);
14450 var result = interceptorFn(value, scope, locals);
14451 // we only return the interceptor's result if the
14452 // initial value is defined (for bind-once)
14453 return isDefined(value) ? result : value;
14454 };
14455
14456 // Propagate $$watchDelegates other then inputsWatchDelegate
14457 if (parsedExpression.$$watchDelegate &&
14458 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14459 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14460 } else if (!interceptorFn.$stateful) {
14461 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14462 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14463 fn.$$watchDelegate = inputsWatchDelegate;
14464 useInputs = !parsedExpression.inputs;
14465 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14466 }
14467
14468 return fn;
14469 }
14470 }];
14471}
14472
14473/**
14474 * @ngdoc service
14475 * @name $q
14476 * @requires $rootScope
14477 *
14478 * @description
14479 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14480 * when they are done processing.
14481 *
14482 * This is an implementation of promises/deferred objects inspired by
14483 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14484 *
14485 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14486 * implementations, and the other which resembles ES6 promises to some degree.
14487 *
14488 * # $q constructor
14489 *
14490 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14491 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14492 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14493 *
14494 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14495 * available yet.
14496 *
14497 * It can be used like so:
14498 *
14499 * ```js
14500 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14501 * // are available in the current lexical scope (they could have been injected or passed in).
14502 *
14503 * function asyncGreet(name) {
14504 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14505 * return $q(function(resolve, reject) {
14506 * setTimeout(function() {
14507 * if (okToGreet(name)) {
14508 * resolve('Hello, ' + name + '!');
14509 * } else {
14510 * reject('Greeting ' + name + ' is not allowed.');
14511 * }
14512 * }, 1000);
14513 * });
14514 * }
14515 *
14516 * var promise = asyncGreet('Robin Hood');
14517 * promise.then(function(greeting) {
14518 * alert('Success: ' + greeting);
14519 * }, function(reason) {
14520 * alert('Failed: ' + reason);
14521 * });
14522 * ```
14523 *
14524 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14525 *
14526 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14527 *
14528 * However, the more traditional CommonJS-style usage is still available, and documented below.
14529 *
14530 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14531 * interface for interacting with an object that represents the result of an action that is
14532 * performed asynchronously, and may or may not be finished at any given point in time.
14533 *
14534 * From the perspective of dealing with error handling, deferred and promise APIs are to
14535 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14536 *
14537 * ```js
14538 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14539 * // are available in the current lexical scope (they could have been injected or passed in).
14540 *
14541 * function asyncGreet(name) {
14542 * var deferred = $q.defer();
14543 *
14544 * setTimeout(function() {
14545 * deferred.notify('About to greet ' + name + '.');
14546 *
14547 * if (okToGreet(name)) {
14548 * deferred.resolve('Hello, ' + name + '!');
14549 * } else {
14550 * deferred.reject('Greeting ' + name + ' is not allowed.');
14551 * }
14552 * }, 1000);
14553 *
14554 * return deferred.promise;
14555 * }
14556 *
14557 * var promise = asyncGreet('Robin Hood');
14558 * promise.then(function(greeting) {
14559 * alert('Success: ' + greeting);
14560 * }, function(reason) {
14561 * alert('Failed: ' + reason);
14562 * }, function(update) {
14563 * alert('Got notification: ' + update);
14564 * });
14565 * ```
14566 *
14567 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14568 * comes in the way of guarantees that promise and deferred APIs make, see
14569 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14570 *
14571 * Additionally the promise api allows for composition that is very hard to do with the
14572 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14573 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14574 * section on serial or parallel joining of promises.
14575 *
14576 * # The Deferred API
14577 *
14578 * A new instance of deferred is constructed by calling `$q.defer()`.
14579 *
14580 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14581 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14582 * of the task.
14583 *
14584 * **Methods**
14585 *
14586 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14587 * constructed via `$q.reject`, the promise will be rejected instead.
14588 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14589 * resolving it with a rejection constructed via `$q.reject`.
14590 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14591 * multiple times before the promise is either resolved or rejected.
14592 *
14593 * **Properties**
14594 *
14595 * - promise – `{Promise}` – promise object associated with this deferred.
14596 *
14597 *
14598 * # The Promise API
14599 *
14600 * A new promise instance is created when a deferred instance is created and can be retrieved by
14601 * calling `deferred.promise`.
14602 *
14603 * The purpose of the promise object is to allow for interested parties to get access to the result
14604 * of the deferred task when it completes.
14605 *
14606 * **Methods**
14607 *
14608 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14609 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14610 * as soon as the result is available. The callbacks are called with a single argument: the result
14611 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14612 * provide a progress indication, before the promise is resolved or rejected.
14613 *
14614 * This method *returns a new promise* which is resolved or rejected via the return value of the
14615 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14616 * with the value which is resolved in that promise using
14617 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14618 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14619 * resolved or rejected from the notifyCallback method.
14620 *
14621 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14622 *
14623 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14624 * but to do so without modifying the final value. This is useful to release resources or do some
14625 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14626 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14627 * more information.
14628 *
14629 * # Chaining promises
14630 *
14631 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14632 * possible to create a chain of promises:
14633 *
14634 * ```js
14635 * promiseB = promiseA.then(function(result) {
14636 * return result + 1;
14637 * });
14638 *
14639 * // promiseB will be resolved immediately after promiseA is resolved and its value
14640 * // will be the result of promiseA incremented by 1
14641 * ```
14642 *
14643 * It is possible to create chains of any length and since a promise can be resolved with another
14644 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14645 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14646 * $http's response interceptors.
14647 *
14648 *
14649 * # Differences between Kris Kowal's Q and $q
14650 *
14651 * There are two main differences:
14652 *
14653 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14654 * mechanism in angular, which means faster propagation of resolution or rejection into your
14655 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14656 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14657 * all the important functionality needed for common async tasks.
14658 *
14659 * # Testing
14660 *
14661 * ```js
14662 * it('should simulate promise', inject(function($q, $rootScope) {
14663 * var deferred = $q.defer();
14664 * var promise = deferred.promise;
14665 * var resolvedValue;
14666 *
14667 * promise.then(function(value) { resolvedValue = value; });
14668 * expect(resolvedValue).toBeUndefined();
14669 *
14670 * // Simulate resolving of promise
14671 * deferred.resolve(123);
14672 * // Note that the 'then' function does not get called synchronously.
14673 * // This is because we want the promise API to always be async, whether or not
14674 * // it got called synchronously or asynchronously.
14675 * expect(resolvedValue).toBeUndefined();
14676 *
14677 * // Propagate promise resolution to 'then' functions using $apply().
14678 * $rootScope.$apply();
14679 * expect(resolvedValue).toEqual(123);
14680 * }));
14681 * ```
14682 *
14683 * @param {function(function, function)} resolver Function which is responsible for resolving or
14684 * rejecting the newly created promise. The first parameter is a function which resolves the
14685 * promise, the second parameter is a function which rejects the promise.
14686 *
14687 * @returns {Promise} The newly created promise.
14688 */
14689function $QProvider() {
14690
14691 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14692 return qFactory(function(callback) {
14693 $rootScope.$evalAsync(callback);
14694 }, $exceptionHandler);
14695 }];
14696}
14697
14698function $$QProvider() {
14699 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14700 return qFactory(function(callback) {
14701 $browser.defer(callback);
14702 }, $exceptionHandler);
14703 }];
14704}
14705
14706/**
14707 * Constructs a promise manager.
14708 *
14709 * @param {function(function)} nextTick Function for executing functions in the next turn.
14710 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14711 * debugging purposes.
14712 * @returns {object} Promise manager.
14713 */
14714function qFactory(nextTick, exceptionHandler) {
14715 var $qMinErr = minErr('$q', TypeError);
14716 function callOnce(self, resolveFn, rejectFn) {
14717 var called = false;
14718 function wrap(fn) {
14719 return function(value) {
14720 if (called) return;
14721 called = true;
14722 fn.call(self, value);
14723 };
14724 }
14725
14726 return [wrap(resolveFn), wrap(rejectFn)];
14727 }
14728
14729 /**
14730 * @ngdoc method
14731 * @name ng.$q#defer
14732 * @kind function
14733 *
14734 * @description
14735 * Creates a `Deferred` object which represents a task which will finish in the future.
14736 *
14737 * @returns {Deferred} Returns a new instance of deferred.
14738 */
14739 var defer = function() {
14740 return new Deferred();
14741 };
14742
14743 function Promise() {
14744 this.$$state = { status: 0 };
14745 }
14746
14747 extend(Promise.prototype, {
14748 then: function(onFulfilled, onRejected, progressBack) {
14749 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14750 return this;
14751 }
14752 var result = new Deferred();
14753
14754 this.$$state.pending = this.$$state.pending || [];
14755 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14756 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14757
14758 return result.promise;
14759 },
14760
14761 "catch": function(callback) {
14762 return this.then(null, callback);
14763 },
14764
14765 "finally": function(callback, progressBack) {
14766 return this.then(function(value) {
14767 return handleCallback(value, true, callback);
14768 }, function(error) {
14769 return handleCallback(error, false, callback);
14770 }, progressBack);
14771 }
14772 });
14773
14774 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14775 function simpleBind(context, fn) {
14776 return function(value) {
14777 fn.call(context, value);
14778 };
14779 }
14780
14781 function processQueue(state) {
14782 var fn, deferred, pending;
14783
14784 pending = state.pending;
14785 state.processScheduled = false;
14786 state.pending = undefined;
14787 for (var i = 0, ii = pending.length; i < ii; ++i) {
14788 deferred = pending[i][0];
14789 fn = pending[i][state.status];
14790 try {
14791 if (isFunction(fn)) {
14792 deferred.resolve(fn(state.value));
14793 } else if (state.status === 1) {
14794 deferred.resolve(state.value);
14795 } else {
14796 deferred.reject(state.value);
14797 }
14798 } catch (e) {
14799 deferred.reject(e);
14800 exceptionHandler(e);
14801 }
14802 }
14803 }
14804
14805 function scheduleProcessQueue(state) {
14806 if (state.processScheduled || !state.pending) return;
14807 state.processScheduled = true;
14808 nextTick(function() { processQueue(state); });
14809 }
14810
14811 function Deferred() {
14812 this.promise = new Promise();
14813 //Necessary to support unbound execution :/
14814 this.resolve = simpleBind(this, this.resolve);
14815 this.reject = simpleBind(this, this.reject);
14816 this.notify = simpleBind(this, this.notify);
14817 }
14818
14819 extend(Deferred.prototype, {
14820 resolve: function(val) {
14821 if (this.promise.$$state.status) return;
14822 if (val === this.promise) {
14823 this.$$reject($qMinErr(
14824 'qcycle',
14825 "Expected promise to be resolved with value other than itself '{0}'",
14826 val));
14827 } else {
14828 this.$$resolve(val);
14829 }
14830
14831 },
14832
14833 $$resolve: function(val) {
14834 var then, fns;
14835
14836 fns = callOnce(this, this.$$resolve, this.$$reject);
14837 try {
14838 if ((isObject(val) || isFunction(val))) then = val && val.then;
14839 if (isFunction(then)) {
14840 this.promise.$$state.status = -1;
14841 then.call(val, fns[0], fns[1], this.notify);
14842 } else {
14843 this.promise.$$state.value = val;
14844 this.promise.$$state.status = 1;
14845 scheduleProcessQueue(this.promise.$$state);
14846 }
14847 } catch (e) {
14848 fns[1](e);
14849 exceptionHandler(e);
14850 }
14851 },
14852
14853 reject: function(reason) {
14854 if (this.promise.$$state.status) return;
14855 this.$$reject(reason);
14856 },
14857
14858 $$reject: function(reason) {
14859 this.promise.$$state.value = reason;
14860 this.promise.$$state.status = 2;
14861 scheduleProcessQueue(this.promise.$$state);
14862 },
14863
14864 notify: function(progress) {
14865 var callbacks = this.promise.$$state.pending;
14866
14867 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14868 nextTick(function() {
14869 var callback, result;
14870 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14871 result = callbacks[i][0];
14872 callback = callbacks[i][3];
14873 try {
14874 result.notify(isFunction(callback) ? callback(progress) : progress);
14875 } catch (e) {
14876 exceptionHandler(e);
14877 }
14878 }
14879 });
14880 }
14881 }
14882 });
14883
14884 /**
14885 * @ngdoc method
14886 * @name $q#reject
14887 * @kind function
14888 *
14889 * @description
14890 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14891 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14892 * a promise chain, you don't need to worry about it.
14893 *
14894 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14895 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14896 * a promise error callback and you want to forward the error to the promise derived from the
14897 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14898 * `reject`.
14899 *
14900 * ```js
14901 * promiseB = promiseA.then(function(result) {
14902 * // success: do something and resolve promiseB
14903 * // with the old or a new result
14904 * return result;
14905 * }, function(reason) {
14906 * // error: handle the error if possible and
14907 * // resolve promiseB with newPromiseOrValue,
14908 * // otherwise forward the rejection to promiseB
14909 * if (canHandle(reason)) {
14910 * // handle the error and recover
14911 * return newPromiseOrValue;
14912 * }
14913 * return $q.reject(reason);
14914 * });
14915 * ```
14916 *
14917 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14918 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14919 */
14920 var reject = function(reason) {
14921 var result = new Deferred();
14922 result.reject(reason);
14923 return result.promise;
14924 };
14925
14926 var makePromise = function makePromise(value, resolved) {
14927 var result = new Deferred();
14928 if (resolved) {
14929 result.resolve(value);
14930 } else {
14931 result.reject(value);
14932 }
14933 return result.promise;
14934 };
14935
14936 var handleCallback = function handleCallback(value, isResolved, callback) {
14937 var callbackOutput = null;
14938 try {
14939 if (isFunction(callback)) callbackOutput = callback();
14940 } catch (e) {
14941 return makePromise(e, false);
14942 }
14943 if (isPromiseLike(callbackOutput)) {
14944 return callbackOutput.then(function() {
14945 return makePromise(value, isResolved);
14946 }, function(error) {
14947 return makePromise(error, false);
14948 });
14949 } else {
14950 return makePromise(value, isResolved);
14951 }
14952 };
14953
14954 /**
14955 * @ngdoc method
14956 * @name $q#when
14957 * @kind function
14958 *
14959 * @description
14960 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
14961 * This is useful when you are dealing with an object that might or might not be a promise, or if
14962 * the promise comes from a source that can't be trusted.
14963 *
14964 * @param {*} value Value or a promise
14965 * @param {Function=} successCallback
14966 * @param {Function=} errorCallback
14967 * @param {Function=} progressCallback
14968 * @returns {Promise} Returns a promise of the passed value or promise
14969 */
14970
14971
14972 var when = function(value, callback, errback, progressBack) {
14973 var result = new Deferred();
14974 result.resolve(value);
14975 return result.promise.then(callback, errback, progressBack);
14976 };
14977
14978 /**
14979 * @ngdoc method
14980 * @name $q#resolve
14981 * @kind function
14982 *
14983 * @description
14984 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
14985 *
14986 * @param {*} value Value or a promise
14987 * @param {Function=} successCallback
14988 * @param {Function=} errorCallback
14989 * @param {Function=} progressCallback
14990 * @returns {Promise} Returns a promise of the passed value or promise
14991 */
14992 var resolve = when;
14993
14994 /**
14995 * @ngdoc method
14996 * @name $q#all
14997 * @kind function
14998 *
14999 * @description
15000 * Combines multiple promises into a single promise that is resolved when all of the input
15001 * promises are resolved.
15002 *
15003 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15004 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15005 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15006 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15007 * with the same rejection value.
15008 */
15009
15010 function all(promises) {
15011 var deferred = new Deferred(),
15012 counter = 0,
15013 results = isArray(promises) ? [] : {};
15014
15015 forEach(promises, function(promise, key) {
15016 counter++;
15017 when(promise).then(function(value) {
15018 if (results.hasOwnProperty(key)) return;
15019 results[key] = value;
15020 if (!(--counter)) deferred.resolve(results);
15021 }, function(reason) {
15022 if (results.hasOwnProperty(key)) return;
15023 deferred.reject(reason);
15024 });
15025 });
15026
15027 if (counter === 0) {
15028 deferred.resolve(results);
15029 }
15030
15031 return deferred.promise;
15032 }
15033
15034 var $Q = function Q(resolver) {
15035 if (!isFunction(resolver)) {
15036 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15037 }
15038
15039 if (!(this instanceof Q)) {
15040 // More useful when $Q is the Promise itself.
15041 return new Q(resolver);
15042 }
15043
15044 var deferred = new Deferred();
15045
15046 function resolveFn(value) {
15047 deferred.resolve(value);
15048 }
15049
15050 function rejectFn(reason) {
15051 deferred.reject(reason);
15052 }
15053
15054 resolver(resolveFn, rejectFn);
15055
15056 return deferred.promise;
15057 };
15058
15059 $Q.defer = defer;
15060 $Q.reject = reject;
15061 $Q.when = when;
15062 $Q.resolve = resolve;
15063 $Q.all = all;
15064
15065 return $Q;
15066}
15067
15068function $$RAFProvider() { //rAF
15069 this.$get = ['$window', '$timeout', function($window, $timeout) {
15070 var requestAnimationFrame = $window.requestAnimationFrame ||
15071 $window.webkitRequestAnimationFrame;
15072
15073 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15074 $window.webkitCancelAnimationFrame ||
15075 $window.webkitCancelRequestAnimationFrame;
15076
15077 var rafSupported = !!requestAnimationFrame;
15078 var raf = rafSupported
15079 ? function(fn) {
15080 var id = requestAnimationFrame(fn);
15081 return function() {
15082 cancelAnimationFrame(id);
15083 };
15084 }
15085 : function(fn) {
15086 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15087 return function() {
15088 $timeout.cancel(timer);
15089 };
15090 };
15091
15092 raf.supported = rafSupported;
15093
15094 return raf;
15095 }];
15096}
15097
15098/**
15099 * DESIGN NOTES
15100 *
15101 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15102 *
15103 * The typical use of scope is to watch the expressions, which most of the time return the same
15104 * value as last time so we optimize the operation.
15105 *
15106 * Closures construction is expensive in terms of speed as well as memory:
15107 * - No closures, instead use prototypical inheritance for API
15108 * - Internal state needs to be stored on scope directly, which means that private state is
15109 * exposed as $$____ properties
15110 *
15111 * Loop operations are optimized by using while(count--) { ... }
15112 * - This means that in order to keep the same order of execution as addition we have to add
15113 * items to the array at the beginning (unshift) instead of at the end (push)
15114 *
15115 * Child scopes are created and removed often
15116 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15117 *
15118 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15119 * in the same way as watch. Watch requires return of the initialization function which is expensive
15120 * to construct.
15121 */
15122
15123
15124/**
15125 * @ngdoc provider
15126 * @name $rootScopeProvider
15127 * @description
15128 *
15129 * Provider for the $rootScope service.
15130 */
15131
15132/**
15133 * @ngdoc method
15134 * @name $rootScopeProvider#digestTtl
15135 * @description
15136 *
15137 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15138 * assuming that the model is unstable.
15139 *
15140 * The current default is 10 iterations.
15141 *
15142 * In complex applications it's possible that the dependencies between `$watch`s will result in
15143 * several digest iterations. However if an application needs more than the default 10 digest
15144 * iterations for its model to stabilize then you should investigate what is causing the model to
15145 * continuously change during the digest.
15146 *
15147 * Increasing the TTL could have performance implications, so you should not change it without
15148 * proper justification.
15149 *
15150 * @param {number} limit The number of digest iterations.
15151 */
15152
15153
15154/**
15155 * @ngdoc service
15156 * @name $rootScope
15157 * @description
15158 *
15159 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15160 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15161 * between the model and the view, via a mechanism for watching the model for changes.
15162 * They also provide event emission/broadcast and subscription facility. See the
15163 * {@link guide/scope developer guide on scopes}.
15164 */
15165function $RootScopeProvider() {
15166 var TTL = 10;
15167 var $rootScopeMinErr = minErr('$rootScope');
15168 var lastDirtyWatch = null;
15169 var applyAsyncId = null;
15170
15171 this.digestTtl = function(value) {
15172 if (arguments.length) {
15173 TTL = value;
15174 }
15175 return TTL;
15176 };
15177
15178 function createChildScopeClass(parent) {
15179 function ChildScope() {
15180 this.$$watchers = this.$$nextSibling =
15181 this.$$childHead = this.$$childTail = null;
15182 this.$$listeners = {};
15183 this.$$listenerCount = {};
15184 this.$$watchersCount = 0;
15185 this.$id = nextUid();
15186 this.$$ChildScope = null;
15187 }
15188 ChildScope.prototype = parent;
15189 return ChildScope;
15190 }
15191
15192 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15193 function($injector, $exceptionHandler, $parse, $browser) {
15194
15195 function destroyChildScope($event) {
15196 $event.currentScope.$$destroyed = true;
15197 }
15198
15199 function cleanUpScope($scope) {
15200
15201 if (msie === 9) {
15202 // There is a memory leak in IE9 if all child scopes are not disconnected
15203 // completely when a scope is destroyed. So this code will recurse up through
15204 // all this scopes children
15205 //
15206 // See issue https://github.com/angular/angular.js/issues/10706
15207 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15208 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15209 }
15210
15211 // The code below works around IE9 and V8's memory leaks
15212 //
15213 // See:
15214 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15215 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15216 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15217
15218 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15219 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15220 }
15221
15222 /**
15223 * @ngdoc type
15224 * @name $rootScope.Scope
15225 *
15226 * @description
15227 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15228 * {@link auto.$injector $injector}. Child scopes are created using the
15229 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15230 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15231 * an in-depth introduction and usage examples.
15232 *
15233 *
15234 * # Inheritance
15235 * A scope can inherit from a parent scope, as in this example:
15236 * ```js
15237 var parent = $rootScope;
15238 var child = parent.$new();
15239
15240 parent.salutation = "Hello";
15241 expect(child.salutation).toEqual('Hello');
15242
15243 child.salutation = "Welcome";
15244 expect(child.salutation).toEqual('Welcome');
15245 expect(parent.salutation).toEqual('Hello');
15246 * ```
15247 *
15248 * When interacting with `Scope` in tests, additional helper methods are available on the
15249 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15250 * details.
15251 *
15252 *
15253 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15254 * provided for the current scope. Defaults to {@link ng}.
15255 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15256 * append/override services provided by `providers`. This is handy
15257 * when unit-testing and having the need to override a default
15258 * service.
15259 * @returns {Object} Newly created scope.
15260 *
15261 */
15262 function Scope() {
15263 this.$id = nextUid();
15264 this.$$phase = this.$parent = this.$$watchers =
15265 this.$$nextSibling = this.$$prevSibling =
15266 this.$$childHead = this.$$childTail = null;
15267 this.$root = this;
15268 this.$$destroyed = false;
15269 this.$$listeners = {};
15270 this.$$listenerCount = {};
15271 this.$$watchersCount = 0;
15272 this.$$isolateBindings = null;
15273 }
15274
15275 /**
15276 * @ngdoc property
15277 * @name $rootScope.Scope#$id
15278 *
15279 * @description
15280 * Unique scope ID (monotonically increasing) useful for debugging.
15281 */
15282
15283 /**
15284 * @ngdoc property
15285 * @name $rootScope.Scope#$parent
15286 *
15287 * @description
15288 * Reference to the parent scope.
15289 */
15290
15291 /**
15292 * @ngdoc property
15293 * @name $rootScope.Scope#$root
15294 *
15295 * @description
15296 * Reference to the root scope.
15297 */
15298
15299 Scope.prototype = {
15300 constructor: Scope,
15301 /**
15302 * @ngdoc method
15303 * @name $rootScope.Scope#$new
15304 * @kind function
15305 *
15306 * @description
15307 * Creates a new child {@link ng.$rootScope.Scope scope}.
15308 *
15309 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15310 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15311 *
15312 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15313 * desired for the scope and its child scopes to be permanently detached from the parent and
15314 * thus stop participating in model change detection and listener notification by invoking.
15315 *
15316 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15317 * parent scope. The scope is isolated, as it can not see parent scope properties.
15318 * When creating widgets, it is useful for the widget to not accidentally read parent
15319 * state.
15320 *
15321 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15322 * of the newly created scope. Defaults to `this` scope if not provided.
15323 * This is used when creating a transclude scope to correctly place it
15324 * in the scope hierarchy while maintaining the correct prototypical
15325 * inheritance.
15326 *
15327 * @returns {Object} The newly created child scope.
15328 *
15329 */
15330 $new: function(isolate, parent) {
15331 var child;
15332
15333 parent = parent || this;
15334
15335 if (isolate) {
15336 child = new Scope();
15337 child.$root = this.$root;
15338 } else {
15339 // Only create a child scope class if somebody asks for one,
15340 // but cache it to allow the VM to optimize lookups.
15341 if (!this.$$ChildScope) {
15342 this.$$ChildScope = createChildScopeClass(this);
15343 }
15344 child = new this.$$ChildScope();
15345 }
15346 child.$parent = parent;
15347 child.$$prevSibling = parent.$$childTail;
15348 if (parent.$$childHead) {
15349 parent.$$childTail.$$nextSibling = child;
15350 parent.$$childTail = child;
15351 } else {
15352 parent.$$childHead = parent.$$childTail = child;
15353 }
15354
15355 // When the new scope is not isolated or we inherit from `this`, and
15356 // the parent scope is destroyed, the property `$$destroyed` is inherited
15357 // prototypically. In all other cases, this property needs to be set
15358 // when the parent scope is destroyed.
15359 // The listener needs to be added after the parent is set
15360 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15361
15362 return child;
15363 },
15364
15365 /**
15366 * @ngdoc method
15367 * @name $rootScope.Scope#$watch
15368 * @kind function
15369 *
15370 * @description
15371 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15372 *
15373 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15374 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15375 * its value when executed multiple times with the same input because it may be executed multiple
15376 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15377 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15378 * - The `listener` is called only when the value from the current `watchExpression` and the
15379 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15380 * see below). Inequality is determined according to reference inequality,
15381 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15382 * via the `!==` Javascript operator, unless `objectEquality == true`
15383 * (see next point)
15384 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15385 * according to the {@link angular.equals} function. To save the value of the object for
15386 * later comparison, the {@link angular.copy} function is used. This therefore means that
15387 * watching complex objects will have adverse memory and performance implications.
15388 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15389 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15390 * iteration limit is 10 to prevent an infinite loop deadlock.
15391 *
15392 *
15393 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15394 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15395 * multiple calls to your `watchExpression` because it will execute multiple times in a
15396 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15397 *
15398 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15399 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15400 * watcher. In rare cases, this is undesirable because the listener is called when the result
15401 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15402 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15403 * listener was called due to initialization.
15404 *
15405 *
15406 *
15407 * # Example
15408 * ```js
15409 // let's assume that scope was dependency injected as the $rootScope
15410 var scope = $rootScope;
15411 scope.name = 'misko';
15412 scope.counter = 0;
15413
15414 expect(scope.counter).toEqual(0);
15415 scope.$watch('name', function(newValue, oldValue) {
15416 scope.counter = scope.counter + 1;
15417 });
15418 expect(scope.counter).toEqual(0);
15419
15420 scope.$digest();
15421 // the listener is always called during the first $digest loop after it was registered
15422 expect(scope.counter).toEqual(1);
15423
15424 scope.$digest();
15425 // but now it will not be called unless the value changes
15426 expect(scope.counter).toEqual(1);
15427
15428 scope.name = 'adam';
15429 scope.$digest();
15430 expect(scope.counter).toEqual(2);
15431
15432
15433
15434 // Using a function as a watchExpression
15435 var food;
15436 scope.foodCounter = 0;
15437 expect(scope.foodCounter).toEqual(0);
15438 scope.$watch(
15439 // This function returns the value being watched. It is called for each turn of the $digest loop
15440 function() { return food; },
15441 // This is the change listener, called when the value returned from the above function changes
15442 function(newValue, oldValue) {
15443 if ( newValue !== oldValue ) {
15444 // Only increment the counter if the value changed
15445 scope.foodCounter = scope.foodCounter + 1;
15446 }
15447 }
15448 );
15449 // No digest has been run so the counter will be zero
15450 expect(scope.foodCounter).toEqual(0);
15451
15452 // Run the digest but since food has not changed count will still be zero
15453 scope.$digest();
15454 expect(scope.foodCounter).toEqual(0);
15455
15456 // Update food and run digest. Now the counter will increment
15457 food = 'cheeseburger';
15458 scope.$digest();
15459 expect(scope.foodCounter).toEqual(1);
15460
15461 * ```
15462 *
15463 *
15464 *
15465 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15466 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15467 * a call to the `listener`.
15468 *
15469 * - `string`: Evaluated as {@link guide/expression expression}
15470 * - `function(scope)`: called with current `scope` as a parameter.
15471 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15472 * of `watchExpression` changes.
15473 *
15474 * - `newVal` contains the current value of the `watchExpression`
15475 * - `oldVal` contains the previous value of the `watchExpression`
15476 * - `scope` refers to the current scope
15477 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15478 * comparing for reference equality.
15479 * @returns {function()} Returns a deregistration function for this listener.
15480 */
15481 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15482 var get = $parse(watchExp);
15483
15484 if (get.$$watchDelegate) {
15485 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15486 }
15487 var scope = this,
15488 array = scope.$$watchers,
15489 watcher = {
15490 fn: listener,
15491 last: initWatchVal,
15492 get: get,
15493 exp: prettyPrintExpression || watchExp,
15494 eq: !!objectEquality
15495 };
15496
15497 lastDirtyWatch = null;
15498
15499 if (!isFunction(listener)) {
15500 watcher.fn = noop;
15501 }
15502
15503 if (!array) {
15504 array = scope.$$watchers = [];
15505 }
15506 // we use unshift since we use a while loop in $digest for speed.
15507 // the while loop reads in reverse order.
15508 array.unshift(watcher);
15509 incrementWatchersCount(this, 1);
15510
15511 return function deregisterWatch() {
15512 if (arrayRemove(array, watcher) >= 0) {
15513 incrementWatchersCount(scope, -1);
15514 }
15515 lastDirtyWatch = null;
15516 };
15517 },
15518
15519 /**
15520 * @ngdoc method
15521 * @name $rootScope.Scope#$watchGroup
15522 * @kind function
15523 *
15524 * @description
15525 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15526 * If any one expression in the collection changes the `listener` is executed.
15527 *
15528 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15529 * call to $digest() to see if any items changes.
15530 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15531 *
15532 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15533 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15534 *
15535 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15536 * expression in `watchExpressions` changes
15537 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15538 * those of `watchExpression`
15539 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15540 * those of `watchExpression`
15541 * The `scope` refers to the current scope.
15542 * @returns {function()} Returns a de-registration function for all listeners.
15543 */
15544 $watchGroup: function(watchExpressions, listener) {
15545 var oldValues = new Array(watchExpressions.length);
15546 var newValues = new Array(watchExpressions.length);
15547 var deregisterFns = [];
15548 var self = this;
15549 var changeReactionScheduled = false;
15550 var firstRun = true;
15551
15552 if (!watchExpressions.length) {
15553 // No expressions means we call the listener ASAP
15554 var shouldCall = true;
15555 self.$evalAsync(function() {
15556 if (shouldCall) listener(newValues, newValues, self);
15557 });
15558 return function deregisterWatchGroup() {
15559 shouldCall = false;
15560 };
15561 }
15562
15563 if (watchExpressions.length === 1) {
15564 // Special case size of one
15565 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15566 newValues[0] = value;
15567 oldValues[0] = oldValue;
15568 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15569 });
15570 }
15571
15572 forEach(watchExpressions, function(expr, i) {
15573 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15574 newValues[i] = value;
15575 oldValues[i] = oldValue;
15576 if (!changeReactionScheduled) {
15577 changeReactionScheduled = true;
15578 self.$evalAsync(watchGroupAction);
15579 }
15580 });
15581 deregisterFns.push(unwatchFn);
15582 });
15583
15584 function watchGroupAction() {
15585 changeReactionScheduled = false;
15586
15587 if (firstRun) {
15588 firstRun = false;
15589 listener(newValues, newValues, self);
15590 } else {
15591 listener(newValues, oldValues, self);
15592 }
15593 }
15594
15595 return function deregisterWatchGroup() {
15596 while (deregisterFns.length) {
15597 deregisterFns.shift()();
15598 }
15599 };
15600 },
15601
15602
15603 /**
15604 * @ngdoc method
15605 * @name $rootScope.Scope#$watchCollection
15606 * @kind function
15607 *
15608 * @description
15609 * Shallow watches the properties of an object and fires whenever any of the properties change
15610 * (for arrays, this implies watching the array items; for object maps, this implies watching
15611 * the properties). If a change is detected, the `listener` callback is fired.
15612 *
15613 * - The `obj` collection is observed via standard $watch operation and is examined on every
15614 * call to $digest() to see if any items have been added, removed, or moved.
15615 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15616 * adding, removing, and moving items belonging to an object or array.
15617 *
15618 *
15619 * # Example
15620 * ```js
15621 $scope.names = ['igor', 'matias', 'misko', 'james'];
15622 $scope.dataCount = 4;
15623
15624 $scope.$watchCollection('names', function(newNames, oldNames) {
15625 $scope.dataCount = newNames.length;
15626 });
15627
15628 expect($scope.dataCount).toEqual(4);
15629 $scope.$digest();
15630
15631 //still at 4 ... no changes
15632 expect($scope.dataCount).toEqual(4);
15633
15634 $scope.names.pop();
15635 $scope.$digest();
15636
15637 //now there's been a change
15638 expect($scope.dataCount).toEqual(3);
15639 * ```
15640 *
15641 *
15642 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15643 * expression value should evaluate to an object or an array which is observed on each
15644 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15645 * collection will trigger a call to the `listener`.
15646 *
15647 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15648 * when a change is detected.
15649 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15650 * - The `oldCollection` object is a copy of the former collection data.
15651 * Due to performance considerations, the`oldCollection` value is computed only if the
15652 * `listener` function declares two or more arguments.
15653 * - The `scope` argument refers to the current scope.
15654 *
15655 * @returns {function()} Returns a de-registration function for this listener. When the
15656 * de-registration function is executed, the internal watch operation is terminated.
15657 */
15658 $watchCollection: function(obj, listener) {
15659 $watchCollectionInterceptor.$stateful = true;
15660
15661 var self = this;
15662 // the current value, updated on each dirty-check run
15663 var newValue;
15664 // a shallow copy of the newValue from the last dirty-check run,
15665 // updated to match newValue during dirty-check run
15666 var oldValue;
15667 // a shallow copy of the newValue from when the last change happened
15668 var veryOldValue;
15669 // only track veryOldValue if the listener is asking for it
15670 var trackVeryOldValue = (listener.length > 1);
15671 var changeDetected = 0;
15672 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15673 var internalArray = [];
15674 var internalObject = {};
15675 var initRun = true;
15676 var oldLength = 0;
15677
15678 function $watchCollectionInterceptor(_value) {
15679 newValue = _value;
15680 var newLength, key, bothNaN, newItem, oldItem;
15681
15682 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15683 if (isUndefined(newValue)) return;
15684
15685 if (!isObject(newValue)) { // if primitive
15686 if (oldValue !== newValue) {
15687 oldValue = newValue;
15688 changeDetected++;
15689 }
15690 } else if (isArrayLike(newValue)) {
15691 if (oldValue !== internalArray) {
15692 // we are transitioning from something which was not an array into array.
15693 oldValue = internalArray;
15694 oldLength = oldValue.length = 0;
15695 changeDetected++;
15696 }
15697
15698 newLength = newValue.length;
15699
15700 if (oldLength !== newLength) {
15701 // if lengths do not match we need to trigger change notification
15702 changeDetected++;
15703 oldValue.length = oldLength = newLength;
15704 }
15705 // copy the items to oldValue and look for changes.
15706 for (var i = 0; i < newLength; i++) {
15707 oldItem = oldValue[i];
15708 newItem = newValue[i];
15709
15710 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15711 if (!bothNaN && (oldItem !== newItem)) {
15712 changeDetected++;
15713 oldValue[i] = newItem;
15714 }
15715 }
15716 } else {
15717 if (oldValue !== internalObject) {
15718 // we are transitioning from something which was not an object into object.
15719 oldValue = internalObject = {};
15720 oldLength = 0;
15721 changeDetected++;
15722 }
15723 // copy the items to oldValue and look for changes.
15724 newLength = 0;
15725 for (key in newValue) {
15726 if (hasOwnProperty.call(newValue, key)) {
15727 newLength++;
15728 newItem = newValue[key];
15729 oldItem = oldValue[key];
15730
15731 if (key in oldValue) {
15732 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15733 if (!bothNaN && (oldItem !== newItem)) {
15734 changeDetected++;
15735 oldValue[key] = newItem;
15736 }
15737 } else {
15738 oldLength++;
15739 oldValue[key] = newItem;
15740 changeDetected++;
15741 }
15742 }
15743 }
15744 if (oldLength > newLength) {
15745 // we used to have more keys, need to find them and destroy them.
15746 changeDetected++;
15747 for (key in oldValue) {
15748 if (!hasOwnProperty.call(newValue, key)) {
15749 oldLength--;
15750 delete oldValue[key];
15751 }
15752 }
15753 }
15754 }
15755 return changeDetected;
15756 }
15757
15758 function $watchCollectionAction() {
15759 if (initRun) {
15760 initRun = false;
15761 listener(newValue, newValue, self);
15762 } else {
15763 listener(newValue, veryOldValue, self);
15764 }
15765
15766 // make a copy for the next time a collection is changed
15767 if (trackVeryOldValue) {
15768 if (!isObject(newValue)) {
15769 //primitive
15770 veryOldValue = newValue;
15771 } else if (isArrayLike(newValue)) {
15772 veryOldValue = new Array(newValue.length);
15773 for (var i = 0; i < newValue.length; i++) {
15774 veryOldValue[i] = newValue[i];
15775 }
15776 } else { // if object
15777 veryOldValue = {};
15778 for (var key in newValue) {
15779 if (hasOwnProperty.call(newValue, key)) {
15780 veryOldValue[key] = newValue[key];
15781 }
15782 }
15783 }
15784 }
15785 }
15786
15787 return this.$watch(changeDetector, $watchCollectionAction);
15788 },
15789
15790 /**
15791 * @ngdoc method
15792 * @name $rootScope.Scope#$digest
15793 * @kind function
15794 *
15795 * @description
15796 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15797 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15798 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15799 * until no more listeners are firing. This means that it is possible to get into an infinite
15800 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15801 * iterations exceeds 10.
15802 *
15803 * Usually, you don't call `$digest()` directly in
15804 * {@link ng.directive:ngController controllers} or in
15805 * {@link ng.$compileProvider#directive directives}.
15806 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15807 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15808 *
15809 * If you want to be notified whenever `$digest()` is called,
15810 * you can register a `watchExpression` function with
15811 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15812 *
15813 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15814 *
15815 * # Example
15816 * ```js
15817 var scope = ...;
15818 scope.name = 'misko';
15819 scope.counter = 0;
15820
15821 expect(scope.counter).toEqual(0);
15822 scope.$watch('name', function(newValue, oldValue) {
15823 scope.counter = scope.counter + 1;
15824 });
15825 expect(scope.counter).toEqual(0);
15826
15827 scope.$digest();
15828 // the listener is always called during the first $digest loop after it was registered
15829 expect(scope.counter).toEqual(1);
15830
15831 scope.$digest();
15832 // but now it will not be called unless the value changes
15833 expect(scope.counter).toEqual(1);
15834
15835 scope.name = 'adam';
15836 scope.$digest();
15837 expect(scope.counter).toEqual(2);
15838 * ```
15839 *
15840 */
15841 $digest: function() {
15842 var watch, value, last,
15843 watchers,
15844 length,
15845 dirty, ttl = TTL,
15846 next, current, target = this,
15847 watchLog = [],
15848 logIdx, logMsg, asyncTask;
15849
15850 beginPhase('$digest');
15851 // Check for changes to browser url that happened in sync before the call to $digest
15852 $browser.$$checkUrlChange();
15853
15854 if (this === $rootScope && applyAsyncId !== null) {
15855 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15856 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15857 $browser.defer.cancel(applyAsyncId);
15858 flushApplyAsync();
15859 }
15860
15861 lastDirtyWatch = null;
15862
15863 do { // "while dirty" loop
15864 dirty = false;
15865 current = target;
15866
15867 while (asyncQueue.length) {
15868 try {
15869 asyncTask = asyncQueue.shift();
15870 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15871 } catch (e) {
15872 $exceptionHandler(e);
15873 }
15874 lastDirtyWatch = null;
15875 }
15876
15877 traverseScopesLoop:
15878 do { // "traverse the scopes" loop
15879 if ((watchers = current.$$watchers)) {
15880 // process our watches
15881 length = watchers.length;
15882 while (length--) {
15883 try {
15884 watch = watchers[length];
15885 // Most common watches are on primitives, in which case we can short
15886 // circuit it with === operator, only when === fails do we use .equals
15887 if (watch) {
15888 if ((value = watch.get(current)) !== (last = watch.last) &&
15889 !(watch.eq
15890 ? equals(value, last)
15891 : (typeof value === 'number' && typeof last === 'number'
15892 && isNaN(value) && isNaN(last)))) {
15893 dirty = true;
15894 lastDirtyWatch = watch;
15895 watch.last = watch.eq ? copy(value, null) : value;
15896 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15897 if (ttl < 5) {
15898 logIdx = 4 - ttl;
15899 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15900 watchLog[logIdx].push({
15901 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15902 newVal: value,
15903 oldVal: last
15904 });
15905 }
15906 } else if (watch === lastDirtyWatch) {
15907 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15908 // have already been tested.
15909 dirty = false;
15910 break traverseScopesLoop;
15911 }
15912 }
15913 } catch (e) {
15914 $exceptionHandler(e);
15915 }
15916 }
15917 }
15918
15919 // Insanity Warning: scope depth-first traversal
15920 // yes, this code is a bit crazy, but it works and we have tests to prove it!
15921 // this piece should be kept in sync with the traversal in $broadcast
15922 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
15923 (current !== target && current.$$nextSibling)))) {
15924 while (current !== target && !(next = current.$$nextSibling)) {
15925 current = current.$parent;
15926 }
15927 }
15928 } while ((current = next));
15929
15930 // `break traverseScopesLoop;` takes us to here
15931
15932 if ((dirty || asyncQueue.length) && !(ttl--)) {
15933 clearPhase();
15934 throw $rootScopeMinErr('infdig',
15935 '{0} $digest() iterations reached. Aborting!\n' +
15936 'Watchers fired in the last 5 iterations: {1}',
15937 TTL, watchLog);
15938 }
15939
15940 } while (dirty || asyncQueue.length);
15941
15942 clearPhase();
15943
15944 while (postDigestQueue.length) {
15945 try {
15946 postDigestQueue.shift()();
15947 } catch (e) {
15948 $exceptionHandler(e);
15949 }
15950 }
15951 },
15952
15953
15954 /**
15955 * @ngdoc event
15956 * @name $rootScope.Scope#$destroy
15957 * @eventType broadcast on scope being destroyed
15958 *
15959 * @description
15960 * Broadcasted when a scope and its children are being destroyed.
15961 *
15962 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
15963 * clean up DOM bindings before an element is removed from the DOM.
15964 */
15965
15966 /**
15967 * @ngdoc method
15968 * @name $rootScope.Scope#$destroy
15969 * @kind function
15970 *
15971 * @description
15972 * Removes the current scope (and all of its children) from the parent scope. Removal implies
15973 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
15974 * propagate to the current scope and its children. Removal also implies that the current
15975 * scope is eligible for garbage collection.
15976 *
15977 * The `$destroy()` is usually used by directives such as
15978 * {@link ng.directive:ngRepeat ngRepeat} for managing the
15979 * unrolling of the loop.
15980 *
15981 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
15982 * Application code can register a `$destroy` event handler that will give it a chance to
15983 * perform any necessary cleanup.
15984 *
15985 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
15986 * clean up DOM bindings before an element is removed from the DOM.
15987 */
15988 $destroy: function() {
15989 // We can't destroy a scope that has been already destroyed.
15990 if (this.$$destroyed) return;
15991 var parent = this.$parent;
15992
15993 this.$broadcast('$destroy');
15994 this.$$destroyed = true;
15995
15996 if (this === $rootScope) {
15997 //Remove handlers attached to window when $rootScope is removed
15998 $browser.$$applicationDestroyed();
15999 }
16000
16001 incrementWatchersCount(this, -this.$$watchersCount);
16002 for (var eventName in this.$$listenerCount) {
16003 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16004 }
16005
16006 // sever all the references to parent scopes (after this cleanup, the current scope should
16007 // not be retained by any of our references and should be eligible for garbage collection)
16008 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16009 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16010 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16011 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16012
16013 // Disable listeners, watchers and apply/digest methods
16014 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16015 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16016 this.$$listeners = {};
16017
16018 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16019 this.$$nextSibling = null;
16020 cleanUpScope(this);
16021 },
16022
16023 /**
16024 * @ngdoc method
16025 * @name $rootScope.Scope#$eval
16026 * @kind function
16027 *
16028 * @description
16029 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16030 * the expression are propagated (uncaught). This is useful when evaluating Angular
16031 * expressions.
16032 *
16033 * # Example
16034 * ```js
16035 var scope = ng.$rootScope.Scope();
16036 scope.a = 1;
16037 scope.b = 2;
16038
16039 expect(scope.$eval('a+b')).toEqual(3);
16040 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16041 * ```
16042 *
16043 * @param {(string|function())=} expression An angular expression to be executed.
16044 *
16045 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16046 * - `function(scope)`: execute the function with the current `scope` parameter.
16047 *
16048 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16049 * @returns {*} The result of evaluating the expression.
16050 */
16051 $eval: function(expr, locals) {
16052 return $parse(expr)(this, locals);
16053 },
16054
16055 /**
16056 * @ngdoc method
16057 * @name $rootScope.Scope#$evalAsync
16058 * @kind function
16059 *
16060 * @description
16061 * Executes the expression on the current scope at a later point in time.
16062 *
16063 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16064 * that:
16065 *
16066 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16067 * rendering).
16068 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16069 * `expression` execution.
16070 *
16071 * Any exceptions from the execution of the expression are forwarded to the
16072 * {@link ng.$exceptionHandler $exceptionHandler} service.
16073 *
16074 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16075 * will be scheduled. However, it is encouraged to always call code that changes the model
16076 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16077 *
16078 * @param {(string|function())=} expression An angular expression to be executed.
16079 *
16080 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16081 * - `function(scope)`: execute the function with the current `scope` parameter.
16082 *
16083 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16084 */
16085 $evalAsync: function(expr, locals) {
16086 // if we are outside of an $digest loop and this is the first time we are scheduling async
16087 // task also schedule async auto-flush
16088 if (!$rootScope.$$phase && !asyncQueue.length) {
16089 $browser.defer(function() {
16090 if (asyncQueue.length) {
16091 $rootScope.$digest();
16092 }
16093 });
16094 }
16095
16096 asyncQueue.push({scope: this, expression: expr, locals: locals});
16097 },
16098
16099 $$postDigest: function(fn) {
16100 postDigestQueue.push(fn);
16101 },
16102
16103 /**
16104 * @ngdoc method
16105 * @name $rootScope.Scope#$apply
16106 * @kind function
16107 *
16108 * @description
16109 * `$apply()` is used to execute an expression in angular from outside of the angular
16110 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16111 * Because we are calling into the angular framework we need to perform proper scope life
16112 * cycle of {@link ng.$exceptionHandler exception handling},
16113 * {@link ng.$rootScope.Scope#$digest executing watches}.
16114 *
16115 * ## Life cycle
16116 *
16117 * # Pseudo-Code of `$apply()`
16118 * ```js
16119 function $apply(expr) {
16120 try {
16121 return $eval(expr);
16122 } catch (e) {
16123 $exceptionHandler(e);
16124 } finally {
16125 $root.$digest();
16126 }
16127 }
16128 * ```
16129 *
16130 *
16131 * Scope's `$apply()` method transitions through the following stages:
16132 *
16133 * 1. The {@link guide/expression expression} is executed using the
16134 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16135 * 2. Any exceptions from the execution of the expression are forwarded to the
16136 * {@link ng.$exceptionHandler $exceptionHandler} service.
16137 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16138 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16139 *
16140 *
16141 * @param {(string|function())=} exp An angular expression to be executed.
16142 *
16143 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16144 * - `function(scope)`: execute the function with current `scope` parameter.
16145 *
16146 * @returns {*} The result of evaluating the expression.
16147 */
16148 $apply: function(expr) {
16149 try {
16150 beginPhase('$apply');
16151 try {
16152 return this.$eval(expr);
16153 } finally {
16154 clearPhase();
16155 }
16156 } catch (e) {
16157 $exceptionHandler(e);
16158 } finally {
16159 try {
16160 $rootScope.$digest();
16161 } catch (e) {
16162 $exceptionHandler(e);
16163 throw e;
16164 }
16165 }
16166 },
16167
16168 /**
16169 * @ngdoc method
16170 * @name $rootScope.Scope#$applyAsync
16171 * @kind function
16172 *
16173 * @description
16174 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16175 * varies across browsers, but is typically around ~10 milliseconds.
16176 *
16177 * This can be used to queue up multiple expressions which need to be evaluated in the same
16178 * digest.
16179 *
16180 * @param {(string|function())=} exp An angular expression to be executed.
16181 *
16182 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16183 * - `function(scope)`: execute the function with current `scope` parameter.
16184 */
16185 $applyAsync: function(expr) {
16186 var scope = this;
16187 expr && applyAsyncQueue.push($applyAsyncExpression);
16188 scheduleApplyAsync();
16189
16190 function $applyAsyncExpression() {
16191 scope.$eval(expr);
16192 }
16193 },
16194
16195 /**
16196 * @ngdoc method
16197 * @name $rootScope.Scope#$on
16198 * @kind function
16199 *
16200 * @description
16201 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16202 * discussion of event life cycle.
16203 *
16204 * The event listener function format is: `function(event, args...)`. The `event` object
16205 * passed into the listener has the following attributes:
16206 *
16207 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16208 * `$broadcast`-ed.
16209 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16210 * event propagates through the scope hierarchy, this property is set to null.
16211 * - `name` - `{string}`: name of the event.
16212 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16213 * further event propagation (available only for events that were `$emit`-ed).
16214 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16215 * to true.
16216 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16217 *
16218 * @param {string} name Event name to listen on.
16219 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16220 * @returns {function()} Returns a deregistration function for this listener.
16221 */
16222 $on: function(name, listener) {
16223 var namedListeners = this.$$listeners[name];
16224 if (!namedListeners) {
16225 this.$$listeners[name] = namedListeners = [];
16226 }
16227 namedListeners.push(listener);
16228
16229 var current = this;
16230 do {
16231 if (!current.$$listenerCount[name]) {
16232 current.$$listenerCount[name] = 0;
16233 }
16234 current.$$listenerCount[name]++;
16235 } while ((current = current.$parent));
16236
16237 var self = this;
16238 return function() {
16239 var indexOfListener = namedListeners.indexOf(listener);
16240 if (indexOfListener !== -1) {
16241 namedListeners[indexOfListener] = null;
16242 decrementListenerCount(self, 1, name);
16243 }
16244 };
16245 },
16246
16247
16248 /**
16249 * @ngdoc method
16250 * @name $rootScope.Scope#$emit
16251 * @kind function
16252 *
16253 * @description
16254 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16255 * registered {@link ng.$rootScope.Scope#$on} listeners.
16256 *
16257 * The event life cycle starts at the scope on which `$emit` was called. All
16258 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16259 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16260 * registered listeners along the way. The event will stop propagating if one of the listeners
16261 * cancels it.
16262 *
16263 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16264 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16265 *
16266 * @param {string} name Event name to emit.
16267 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16268 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16269 */
16270 $emit: function(name, args) {
16271 var empty = [],
16272 namedListeners,
16273 scope = this,
16274 stopPropagation = false,
16275 event = {
16276 name: name,
16277 targetScope: scope,
16278 stopPropagation: function() {stopPropagation = true;},
16279 preventDefault: function() {
16280 event.defaultPrevented = true;
16281 },
16282 defaultPrevented: false
16283 },
16284 listenerArgs = concat([event], arguments, 1),
16285 i, length;
16286
16287 do {
16288 namedListeners = scope.$$listeners[name] || empty;
16289 event.currentScope = scope;
16290 for (i = 0, length = namedListeners.length; i < length; i++) {
16291
16292 // if listeners were deregistered, defragment the array
16293 if (!namedListeners[i]) {
16294 namedListeners.splice(i, 1);
16295 i--;
16296 length--;
16297 continue;
16298 }
16299 try {
16300 //allow all listeners attached to the current scope to run
16301 namedListeners[i].apply(null, listenerArgs);
16302 } catch (e) {
16303 $exceptionHandler(e);
16304 }
16305 }
16306 //if any listener on the current scope stops propagation, prevent bubbling
16307 if (stopPropagation) {
16308 event.currentScope = null;
16309 return event;
16310 }
16311 //traverse upwards
16312 scope = scope.$parent;
16313 } while (scope);
16314
16315 event.currentScope = null;
16316
16317 return event;
16318 },
16319
16320
16321 /**
16322 * @ngdoc method
16323 * @name $rootScope.Scope#$broadcast
16324 * @kind function
16325 *
16326 * @description
16327 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16328 * registered {@link ng.$rootScope.Scope#$on} listeners.
16329 *
16330 * The event life cycle starts at the scope on which `$broadcast` was called. All
16331 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16332 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16333 * scope and calls all registered listeners along the way. The event cannot be canceled.
16334 *
16335 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16336 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16337 *
16338 * @param {string} name Event name to broadcast.
16339 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16340 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16341 */
16342 $broadcast: function(name, args) {
16343 var target = this,
16344 current = target,
16345 next = target,
16346 event = {
16347 name: name,
16348 targetScope: target,
16349 preventDefault: function() {
16350 event.defaultPrevented = true;
16351 },
16352 defaultPrevented: false
16353 };
16354
16355 if (!target.$$listenerCount[name]) return event;
16356
16357 var listenerArgs = concat([event], arguments, 1),
16358 listeners, i, length;
16359
16360 //down while you can, then up and next sibling or up and next sibling until back at root
16361 while ((current = next)) {
16362 event.currentScope = current;
16363 listeners = current.$$listeners[name] || [];
16364 for (i = 0, length = listeners.length; i < length; i++) {
16365 // if listeners were deregistered, defragment the array
16366 if (!listeners[i]) {
16367 listeners.splice(i, 1);
16368 i--;
16369 length--;
16370 continue;
16371 }
16372
16373 try {
16374 listeners[i].apply(null, listenerArgs);
16375 } catch (e) {
16376 $exceptionHandler(e);
16377 }
16378 }
16379
16380 // Insanity Warning: scope depth-first traversal
16381 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16382 // this piece should be kept in sync with the traversal in $digest
16383 // (though it differs due to having the extra check for $$listenerCount)
16384 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16385 (current !== target && current.$$nextSibling)))) {
16386 while (current !== target && !(next = current.$$nextSibling)) {
16387 current = current.$parent;
16388 }
16389 }
16390 }
16391
16392 event.currentScope = null;
16393 return event;
16394 }
16395 };
16396
16397 var $rootScope = new Scope();
16398
16399 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16400 var asyncQueue = $rootScope.$$asyncQueue = [];
16401 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16402 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16403
16404 return $rootScope;
16405
16406
16407 function beginPhase(phase) {
16408 if ($rootScope.$$phase) {
16409 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16410 }
16411
16412 $rootScope.$$phase = phase;
16413 }
16414
16415 function clearPhase() {
16416 $rootScope.$$phase = null;
16417 }
16418
16419 function incrementWatchersCount(current, count) {
16420 do {
16421 current.$$watchersCount += count;
16422 } while ((current = current.$parent));
16423 }
16424
16425 function decrementListenerCount(current, count, name) {
16426 do {
16427 current.$$listenerCount[name] -= count;
16428
16429 if (current.$$listenerCount[name] === 0) {
16430 delete current.$$listenerCount[name];
16431 }
16432 } while ((current = current.$parent));
16433 }
16434
16435 /**
16436 * function used as an initial value for watchers.
16437 * because it's unique we can easily tell it apart from other values
16438 */
16439 function initWatchVal() {}
16440
16441 function flushApplyAsync() {
16442 while (applyAsyncQueue.length) {
16443 try {
16444 applyAsyncQueue.shift()();
16445 } catch (e) {
16446 $exceptionHandler(e);
16447 }
16448 }
16449 applyAsyncId = null;
16450 }
16451
16452 function scheduleApplyAsync() {
16453 if (applyAsyncId === null) {
16454 applyAsyncId = $browser.defer(function() {
16455 $rootScope.$apply(flushApplyAsync);
16456 });
16457 }
16458 }
16459 }];
16460}
16461
16462/**
16463 * @description
16464 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16465 */
16466function $$SanitizeUriProvider() {
16467 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16468 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16469
16470 /**
16471 * @description
16472 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16473 * urls during a[href] sanitization.
16474 *
16475 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16476 *
16477 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16478 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16479 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16480 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16481 *
16482 * @param {RegExp=} regexp New regexp to whitelist urls with.
16483 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16484 * chaining otherwise.
16485 */
16486 this.aHrefSanitizationWhitelist = function(regexp) {
16487 if (isDefined(regexp)) {
16488 aHrefSanitizationWhitelist = regexp;
16489 return this;
16490 }
16491 return aHrefSanitizationWhitelist;
16492 };
16493
16494
16495 /**
16496 * @description
16497 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16498 * urls during img[src] sanitization.
16499 *
16500 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16501 *
16502 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16503 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16504 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16505 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16506 *
16507 * @param {RegExp=} regexp New regexp to whitelist urls with.
16508 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16509 * chaining otherwise.
16510 */
16511 this.imgSrcSanitizationWhitelist = function(regexp) {
16512 if (isDefined(regexp)) {
16513 imgSrcSanitizationWhitelist = regexp;
16514 return this;
16515 }
16516 return imgSrcSanitizationWhitelist;
16517 };
16518
16519 this.$get = function() {
16520 return function sanitizeUri(uri, isImage) {
16521 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16522 var normalizedVal;
16523 normalizedVal = urlResolve(uri).href;
16524 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16525 return 'unsafe:' + normalizedVal;
16526 }
16527 return uri;
16528 };
16529 };
16530}
16531
16532/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16533 * Any commits to this file should be reviewed with security in mind. *
16534 * Changes to this file can potentially create security vulnerabilities. *
16535 * An approval from 2 Core members with history of modifying *
16536 * this file is required. *
16537 * *
16538 * Does the change somehow allow for arbitrary javascript to be executed? *
16539 * Or allows for someone to change the prototype of built-in objects? *
16540 * Or gives undesired access to variables likes document or window? *
16541 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16542
16543var $sceMinErr = minErr('$sce');
16544
16545var SCE_CONTEXTS = {
16546 HTML: 'html',
16547 CSS: 'css',
16548 URL: 'url',
16549 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16550 // url. (e.g. ng-include, script src, templateUrl)
16551 RESOURCE_URL: 'resourceUrl',
16552 JS: 'js'
16553};
16554
16555// Helper functions follow.
16556
16557function adjustMatcher(matcher) {
16558 if (matcher === 'self') {
16559 return matcher;
16560 } else if (isString(matcher)) {
16561 // Strings match exactly except for 2 wildcards - '*' and '**'.
16562 // '*' matches any character except those from the set ':/.?&'.
16563 // '**' matches any character (like .* in a RegExp).
16564 // More than 2 *'s raises an error as it's ill defined.
16565 if (matcher.indexOf('***') > -1) {
16566 throw $sceMinErr('iwcard',
16567 'Illegal sequence *** in string matcher. String: {0}', matcher);
16568 }
16569 matcher = escapeForRegexp(matcher).
16570 replace('\\*\\*', '.*').
16571 replace('\\*', '[^:/.?&;]*');
16572 return new RegExp('^' + matcher + '$');
16573 } else if (isRegExp(matcher)) {
16574 // The only other type of matcher allowed is a Regexp.
16575 // Match entire URL / disallow partial matches.
16576 // Flags are reset (i.e. no global, ignoreCase or multiline)
16577 return new RegExp('^' + matcher.source + '$');
16578 } else {
16579 throw $sceMinErr('imatcher',
16580 'Matchers may only be "self", string patterns or RegExp objects');
16581 }
16582}
16583
16584
16585function adjustMatchers(matchers) {
16586 var adjustedMatchers = [];
16587 if (isDefined(matchers)) {
16588 forEach(matchers, function(matcher) {
16589 adjustedMatchers.push(adjustMatcher(matcher));
16590 });
16591 }
16592 return adjustedMatchers;
16593}
16594
16595
16596/**
16597 * @ngdoc service
16598 * @name $sceDelegate
16599 * @kind function
16600 *
16601 * @description
16602 *
16603 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16604 * Contextual Escaping (SCE)} services to AngularJS.
16605 *
16606 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16607 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16608 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16609 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16610 * work because `$sce` delegates to `$sceDelegate` for these operations.
16611 *
16612 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16613 *
16614 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16615 * can override it completely to change the behavior of `$sce`, the common case would
16616 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16617 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16618 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16619 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16620 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16621 */
16622
16623/**
16624 * @ngdoc provider
16625 * @name $sceDelegateProvider
16626 * @description
16627 *
16628 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16629 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16630 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16631 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16632 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16633 *
16634 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16635 * Strict Contextual Escaping (SCE)}.
16636 *
16637 * **Example**: Consider the following case. <a name="example"></a>
16638 *
16639 * - your app is hosted at url `http://myapp.example.com/`
16640 * - but some of your templates are hosted on other domains you control such as
16641 * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc.
16642 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16643 *
16644 * Here is what a secure configuration for this scenario might look like:
16645 *
16646 * ```
16647 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16648 * $sceDelegateProvider.resourceUrlWhitelist([
16649 * // Allow same origin resource loads.
16650 * 'self',
16651 * // Allow loading from our assets domain. Notice the difference between * and **.
16652 * 'http://srv*.assets.example.com/**'
16653 * ]);
16654 *
16655 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16656 * $sceDelegateProvider.resourceUrlBlacklist([
16657 * 'http://myapp.example.com/clickThru**'
16658 * ]);
16659 * });
16660 * ```
16661 */
16662
16663function $SceDelegateProvider() {
16664 this.SCE_CONTEXTS = SCE_CONTEXTS;
16665
16666 // Resource URLs can also be trusted by policy.
16667 var resourceUrlWhitelist = ['self'],
16668 resourceUrlBlacklist = [];
16669
16670 /**
16671 * @ngdoc method
16672 * @name $sceDelegateProvider#resourceUrlWhitelist
16673 * @kind function
16674 *
16675 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16676 * provided. This must be an array or null. A snapshot of this array is used so further
16677 * changes to the array are ignored.
16678 *
16679 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16680 * allowed in this array.
16681 *
16682 * Note: **an empty whitelist array will block all URLs**!
16683 *
16684 * @return {Array} the currently set whitelist array.
16685 *
16686 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16687 * same origin resource requests.
16688 *
16689 * @description
16690 * Sets/Gets the whitelist of trusted resource URLs.
16691 */
16692 this.resourceUrlWhitelist = function(value) {
16693 if (arguments.length) {
16694 resourceUrlWhitelist = adjustMatchers(value);
16695 }
16696 return resourceUrlWhitelist;
16697 };
16698
16699 /**
16700 * @ngdoc method
16701 * @name $sceDelegateProvider#resourceUrlBlacklist
16702 * @kind function
16703 *
16704 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16705 * provided. This must be an array or null. A snapshot of this array is used so further
16706 * changes to the array are ignored.
16707 *
16708 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16709 * allowed in this array.
16710 *
16711 * The typical usage for the blacklist is to **block
16712 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16713 * these would otherwise be trusted but actually return content from the redirected domain.
16714 *
16715 * Finally, **the blacklist overrides the whitelist** and has the final say.
16716 *
16717 * @return {Array} the currently set blacklist array.
16718 *
16719 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16720 * is no blacklist.)
16721 *
16722 * @description
16723 * Sets/Gets the blacklist of trusted resource URLs.
16724 */
16725
16726 this.resourceUrlBlacklist = function(value) {
16727 if (arguments.length) {
16728 resourceUrlBlacklist = adjustMatchers(value);
16729 }
16730 return resourceUrlBlacklist;
16731 };
16732
16733 this.$get = ['$injector', function($injector) {
16734
16735 var htmlSanitizer = function htmlSanitizer(html) {
16736 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16737 };
16738
16739 if ($injector.has('$sanitize')) {
16740 htmlSanitizer = $injector.get('$sanitize');
16741 }
16742
16743
16744 function matchUrl(matcher, parsedUrl) {
16745 if (matcher === 'self') {
16746 return urlIsSameOrigin(parsedUrl);
16747 } else {
16748 // definitely a regex. See adjustMatchers()
16749 return !!matcher.exec(parsedUrl.href);
16750 }
16751 }
16752
16753 function isResourceUrlAllowedByPolicy(url) {
16754 var parsedUrl = urlResolve(url.toString());
16755 var i, n, allowed = false;
16756 // Ensure that at least one item from the whitelist allows this url.
16757 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16758 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16759 allowed = true;
16760 break;
16761 }
16762 }
16763 if (allowed) {
16764 // Ensure that no item from the blacklist blocked this url.
16765 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16766 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16767 allowed = false;
16768 break;
16769 }
16770 }
16771 }
16772 return allowed;
16773 }
16774
16775 function generateHolderType(Base) {
16776 var holderType = function TrustedValueHolderType(trustedValue) {
16777 this.$$unwrapTrustedValue = function() {
16778 return trustedValue;
16779 };
16780 };
16781 if (Base) {
16782 holderType.prototype = new Base();
16783 }
16784 holderType.prototype.valueOf = function sceValueOf() {
16785 return this.$$unwrapTrustedValue();
16786 };
16787 holderType.prototype.toString = function sceToString() {
16788 return this.$$unwrapTrustedValue().toString();
16789 };
16790 return holderType;
16791 }
16792
16793 var trustedValueHolderBase = generateHolderType(),
16794 byType = {};
16795
16796 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16797 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16798 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16799 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16800 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16801
16802 /**
16803 * @ngdoc method
16804 * @name $sceDelegate#trustAs
16805 *
16806 * @description
16807 * Returns an object that is trusted by angular for use in specified strict
16808 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16809 * attribute interpolation, any dom event binding attribute interpolation
16810 * such as for onclick, etc.) that uses the provided value.
16811 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16812 *
16813 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16814 * resourceUrl, html, js and css.
16815 * @param {*} value The value that that should be considered trusted/safe.
16816 * @returns {*} A value that can be used to stand in for the provided `value` in places
16817 * where Angular expects a $sce.trustAs() return value.
16818 */
16819 function trustAs(type, trustedValue) {
16820 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16821 if (!Constructor) {
16822 throw $sceMinErr('icontext',
16823 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16824 type, trustedValue);
16825 }
16826 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16827 return trustedValue;
16828 }
16829 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16830 // mutable objects, we ensure here that the value passed in is actually a string.
16831 if (typeof trustedValue !== 'string') {
16832 throw $sceMinErr('itype',
16833 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16834 type);
16835 }
16836 return new Constructor(trustedValue);
16837 }
16838
16839 /**
16840 * @ngdoc method
16841 * @name $sceDelegate#valueOf
16842 *
16843 * @description
16844 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16845 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16846 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16847 *
16848 * If the passed parameter is not a value that had been returned by {@link
16849 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16850 *
16851 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16852 * call or anything else.
16853 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16854 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16855 * `value` unchanged.
16856 */
16857 function valueOf(maybeTrusted) {
16858 if (maybeTrusted instanceof trustedValueHolderBase) {
16859 return maybeTrusted.$$unwrapTrustedValue();
16860 } else {
16861 return maybeTrusted;
16862 }
16863 }
16864
16865 /**
16866 * @ngdoc method
16867 * @name $sceDelegate#getTrusted
16868 *
16869 * @description
16870 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16871 * returns the originally supplied value if the queried context type is a supertype of the
16872 * created type. If this condition isn't satisfied, throws an exception.
16873 *
16874 * @param {string} type The kind of context in which this value is to be used.
16875 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16876 * `$sceDelegate.trustAs`} call.
16877 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16878 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16879 */
16880 function getTrusted(type, maybeTrusted) {
16881 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16882 return maybeTrusted;
16883 }
16884 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16885 if (constructor && maybeTrusted instanceof constructor) {
16886 return maybeTrusted.$$unwrapTrustedValue();
16887 }
16888 // If we get here, then we may only take one of two actions.
16889 // 1. sanitize the value for the requested type, or
16890 // 2. throw an exception.
16891 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16892 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16893 return maybeTrusted;
16894 } else {
16895 throw $sceMinErr('insecurl',
16896 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16897 maybeTrusted.toString());
16898 }
16899 } else if (type === SCE_CONTEXTS.HTML) {
16900 return htmlSanitizer(maybeTrusted);
16901 }
16902 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16903 }
16904
16905 return { trustAs: trustAs,
16906 getTrusted: getTrusted,
16907 valueOf: valueOf };
16908 }];
16909}
16910
16911
16912/**
16913 * @ngdoc provider
16914 * @name $sceProvider
16915 * @description
16916 *
16917 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16918 * - enable/disable Strict Contextual Escaping (SCE) in a module
16919 * - override the default implementation with a custom delegate
16920 *
16921 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
16922 */
16923
16924/* jshint maxlen: false*/
16925
16926/**
16927 * @ngdoc service
16928 * @name $sce
16929 * @kind function
16930 *
16931 * @description
16932 *
16933 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
16934 *
16935 * # Strict Contextual Escaping
16936 *
16937 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
16938 * contexts to result in a value that is marked as safe to use for that context. One example of
16939 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
16940 * to these contexts as privileged or SCE contexts.
16941 *
16942 * As of version 1.2, Angular ships with SCE enabled by default.
16943 *
16944 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
16945 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
16946 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
16947 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
16948 * to the top of your HTML document.
16949 *
16950 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
16951 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
16952 *
16953 * Here's an example of a binding in a privileged context:
16954 *
16955 * ```
16956 * <input ng-model="userHtml" aria-label="User input">
16957 * <div ng-bind-html="userHtml"></div>
16958 * ```
16959 *
16960 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
16961 * disabled, this application allows the user to render arbitrary HTML into the DIV.
16962 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
16963 * bindings. (HTML is just one example of a context where rendering user controlled input creates
16964 * security vulnerabilities.)
16965 *
16966 * For the case of HTML, you might use a library, either on the client side, or on the server side,
16967 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
16968 *
16969 * How would you ensure that every place that used these types of bindings was bound to a value that
16970 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
16971 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
16972 * properties/fields and forgot to update the binding to the sanitized value?
16973 *
16974 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
16975 * determine that something explicitly says it's safe to use a value for binding in that
16976 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
16977 * for those values that you can easily tell are safe - because they were received from your server,
16978 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
16979 * allowing only the files in a specific directory to do this. Ensuring that the internal API
16980 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
16981 *
16982 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
16983 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
16984 * obtain values that will be accepted by SCE / privileged contexts.
16985 *
16986 *
16987 * ## How does it work?
16988 *
16989 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
16990 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
16991 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
16992 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
16993 *
16994 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
16995 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
16996 * simplified):
16997 *
16998 * ```
16999 * var ngBindHtmlDirective = ['$sce', function($sce) {
17000 * return function(scope, element, attr) {
17001 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17002 * element.html(value || '');
17003 * });
17004 * };
17005 * }];
17006 * ```
17007 *
17008 * ## Impact on loading templates
17009 *
17010 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17011 * `templateUrl`'s specified by {@link guide/directive directives}.
17012 *
17013 * By default, Angular only loads templates from the same domain and protocol as the application
17014 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17015 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17016 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17017 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17018 *
17019 * *Please note*:
17020 * The browser's
17021 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17022 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17023 * policy apply in addition to this and may further restrict whether the template is successfully
17024 * loaded. This means that without the right CORS policy, loading templates from a different domain
17025 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17026 * browsers.
17027 *
17028 * ## This feels like too much overhead
17029 *
17030 * It's important to remember that SCE only applies to interpolation expressions.
17031 *
17032 * If your expressions are constant literals, they're automatically trusted and you don't need to
17033 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17034 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17035 *
17036 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17037 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17038 *
17039 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17040 * templates in `ng-include` from your application's domain without having to even know about SCE.
17041 * It blocks loading templates from other domains or loading templates over http from an https
17042 * served document. You can change these by setting your own custom {@link
17043 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17044 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17045 *
17046 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17047 * application that's secure and can be audited to verify that with much more ease than bolting
17048 * security onto an application later.
17049 *
17050 * <a name="contexts"></a>
17051 * ## What trusted context types are supported?
17052 *
17053 * | Context | Notes |
17054 * |---------------------|----------------|
17055 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
17056 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17057 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. |
17058 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
17059 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
17060 *
17061 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17062 *
17063 * Each element in these arrays must be one of the following:
17064 *
17065 * - **'self'**
17066 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17067 * domain** as the application document using the **same protocol**.
17068 * - **String** (except the special value `'self'`)
17069 * - The string is matched against the full *normalized / absolute URL* of the resource
17070 * being tested (substring matches are not good enough.)
17071 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17072 * match themselves.
17073 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17074 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17075 * in a whitelist.
17076 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17077 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17078 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17079 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17080 * http://foo.example.com/templates/**).
17081 * - **RegExp** (*see caveat below*)
17082 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17083 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17084 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17085 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17086 * small number of cases. A `.` character in the regex used when matching the scheme or a
17087 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17088 * is highly recommended to use the string patterns and only fall back to regular expressions
17089 * as a last resort.
17090 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17091 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17092 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17093 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17094 * - If you are generating your JavaScript from some other templating engine (not
17095 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17096 * remember to escape your regular expression (and be aware that you might need more than
17097 * one level of escaping depending on your templating engine and the way you interpolated
17098 * the value.) Do make use of your platform's escaping mechanism as it might be good
17099 * enough before coding your own. E.g. Ruby has
17100 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17101 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17102 * Javascript lacks a similar built in function for escaping. Take a look at Google
17103 * Closure library's [goog.string.regExpEscape(s)](
17104 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17105 *
17106 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17107 *
17108 * ## Show me an example using SCE.
17109 *
17110 * <example module="mySceApp" deps="angular-sanitize.js">
17111 * <file name="index.html">
17112 * <div ng-controller="AppController as myCtrl">
17113 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17114 * <b>User comments</b><br>
17115 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17116 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17117 * exploit.
17118 * <div class="well">
17119 * <div ng-repeat="userComment in myCtrl.userComments">
17120 * <b>{{userComment.name}}</b>:
17121 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17122 * <br>
17123 * </div>
17124 * </div>
17125 * </div>
17126 * </file>
17127 *
17128 * <file name="script.js">
17129 * angular.module('mySceApp', ['ngSanitize'])
17130 * .controller('AppController', ['$http', '$templateCache', '$sce',
17131 * function($http, $templateCache, $sce) {
17132 * var self = this;
17133 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17134 * self.userComments = userComments;
17135 * });
17136 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17137 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
17138 * 'sanitization.&quot;">Hover over this text.</span>');
17139 * }]);
17140 * </file>
17141 *
17142 * <file name="test_data.json">
17143 * [
17144 * { "name": "Alice",
17145 * "htmlComment":
17146 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17147 * },
17148 * { "name": "Bob",
17149 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17150 * }
17151 * ]
17152 * </file>
17153 *
17154 * <file name="protractor.js" type="protractor">
17155 * describe('SCE doc demo', function() {
17156 * it('should sanitize untrusted values', function() {
17157 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17158 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17159 * });
17160 *
17161 * it('should NOT sanitize explicitly trusted values', function() {
17162 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17163 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
17164 * 'sanitization.&quot;">Hover over this text.</span>');
17165 * });
17166 * });
17167 * </file>
17168 * </example>
17169 *
17170 *
17171 *
17172 * ## Can I disable SCE completely?
17173 *
17174 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17175 * for little coding overhead. It will be much harder to take an SCE disabled application and
17176 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17177 * for cases where you have a lot of existing code that was written before SCE was introduced and
17178 * you're migrating them a module at a time.
17179 *
17180 * That said, here's how you can completely disable SCE:
17181 *
17182 * ```
17183 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17184 * // Completely disable SCE. For demonstration purposes only!
17185 * // Do not use in new projects.
17186 * $sceProvider.enabled(false);
17187 * });
17188 * ```
17189 *
17190 */
17191/* jshint maxlen: 100 */
17192
17193function $SceProvider() {
17194 var enabled = true;
17195
17196 /**
17197 * @ngdoc method
17198 * @name $sceProvider#enabled
17199 * @kind function
17200 *
17201 * @param {boolean=} value If provided, then enables/disables SCE.
17202 * @return {boolean} true if SCE is enabled, false otherwise.
17203 *
17204 * @description
17205 * Enables/disables SCE and returns the current value.
17206 */
17207 this.enabled = function(value) {
17208 if (arguments.length) {
17209 enabled = !!value;
17210 }
17211 return enabled;
17212 };
17213
17214
17215 /* Design notes on the default implementation for SCE.
17216 *
17217 * The API contract for the SCE delegate
17218 * -------------------------------------
17219 * The SCE delegate object must provide the following 3 methods:
17220 *
17221 * - trustAs(contextEnum, value)
17222 * This method is used to tell the SCE service that the provided value is OK to use in the
17223 * contexts specified by contextEnum. It must return an object that will be accepted by
17224 * getTrusted() for a compatible contextEnum and return this value.
17225 *
17226 * - valueOf(value)
17227 * For values that were not produced by trustAs(), return them as is. For values that were
17228 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17229 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17230 * such a value.
17231 *
17232 * - getTrusted(contextEnum, value)
17233 * This function should return the a value that is safe to use in the context specified by
17234 * contextEnum or throw and exception otherwise.
17235 *
17236 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17237 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17238 * instance, an implementation could maintain a registry of all trusted objects by context. In
17239 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17240 * return the same object passed in if it was found in the registry under a compatible context or
17241 * throw an exception otherwise. An implementation might only wrap values some of the time based
17242 * on some criteria. getTrusted() might return a value and not throw an exception for special
17243 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17244 *
17245 *
17246 * A note on the inheritance model for SCE contexts
17247 * ------------------------------------------------
17248 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17249 * is purely an implementation details.
17250 *
17251 * The contract is simply this:
17252 *
17253 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17254 * will also succeed.
17255 *
17256 * Inheritance happens to capture this in a natural way. In some future, we
17257 * may not use inheritance anymore. That is OK because no code outside of
17258 * sce.js and sceSpecs.js would need to be aware of this detail.
17259 */
17260
17261 this.$get = ['$parse', '$sceDelegate', function(
17262 $parse, $sceDelegate) {
17263 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17264 // the "expression(javascript expression)" syntax which is insecure.
17265 if (enabled && msie < 8) {
17266 throw $sceMinErr('iequirks',
17267 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17268 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17269 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17270 }
17271
17272 var sce = shallowCopy(SCE_CONTEXTS);
17273
17274 /**
17275 * @ngdoc method
17276 * @name $sce#isEnabled
17277 * @kind function
17278 *
17279 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17280 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17281 *
17282 * @description
17283 * Returns a boolean indicating if SCE is enabled.
17284 */
17285 sce.isEnabled = function() {
17286 return enabled;
17287 };
17288 sce.trustAs = $sceDelegate.trustAs;
17289 sce.getTrusted = $sceDelegate.getTrusted;
17290 sce.valueOf = $sceDelegate.valueOf;
17291
17292 if (!enabled) {
17293 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17294 sce.valueOf = identity;
17295 }
17296
17297 /**
17298 * @ngdoc method
17299 * @name $sce#parseAs
17300 *
17301 * @description
17302 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17303 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17304 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17305 * *result*)}
17306 *
17307 * @param {string} type The kind of SCE context in which this result will be used.
17308 * @param {string} expression String expression to compile.
17309 * @returns {function(context, locals)} a function which represents the compiled expression:
17310 *
17311 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17312 * are evaluated against (typically a scope object).
17313 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17314 * `context`.
17315 */
17316 sce.parseAs = function sceParseAs(type, expr) {
17317 var parsed = $parse(expr);
17318 if (parsed.literal && parsed.constant) {
17319 return parsed;
17320 } else {
17321 return $parse(expr, function(value) {
17322 return sce.getTrusted(type, value);
17323 });
17324 }
17325 };
17326
17327 /**
17328 * @ngdoc method
17329 * @name $sce#trustAs
17330 *
17331 * @description
17332 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17333 * returns an object that is trusted by angular for use in specified strict contextual
17334 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17335 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17336 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17337 * escaping.
17338 *
17339 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17340 * resourceUrl, html, js and css.
17341 * @param {*} value The value that that should be considered trusted/safe.
17342 * @returns {*} A value that can be used to stand in for the provided `value` in places
17343 * where Angular expects a $sce.trustAs() return value.
17344 */
17345
17346 /**
17347 * @ngdoc method
17348 * @name $sce#trustAsHtml
17349 *
17350 * @description
17351 * Shorthand method. `$sce.trustAsHtml(value)` →
17352 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17353 *
17354 * @param {*} value The value to trustAs.
17355 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17356 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17357 * only accept expressions that are either literal constants or are the
17358 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17359 */
17360
17361 /**
17362 * @ngdoc method
17363 * @name $sce#trustAsUrl
17364 *
17365 * @description
17366 * Shorthand method. `$sce.trustAsUrl(value)` →
17367 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17368 *
17369 * @param {*} value The value to trustAs.
17370 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17371 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17372 * only accept expressions that are either literal constants or are the
17373 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17374 */
17375
17376 /**
17377 * @ngdoc method
17378 * @name $sce#trustAsResourceUrl
17379 *
17380 * @description
17381 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17382 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17383 *
17384 * @param {*} value The value to trustAs.
17385 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17386 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17387 * only accept expressions that are either literal constants or are the return
17388 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17389 */
17390
17391 /**
17392 * @ngdoc method
17393 * @name $sce#trustAsJs
17394 *
17395 * @description
17396 * Shorthand method. `$sce.trustAsJs(value)` →
17397 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17398 *
17399 * @param {*} value The value to trustAs.
17400 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17401 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17402 * only accept expressions that are either literal constants or are the
17403 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17404 */
17405
17406 /**
17407 * @ngdoc method
17408 * @name $sce#getTrusted
17409 *
17410 * @description
17411 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17412 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17413 * originally supplied value if the queried context type is a supertype of the created type.
17414 * If this condition isn't satisfied, throws an exception.
17415 *
17416 * @param {string} type The kind of context in which this value is to be used.
17417 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17418 * call.
17419 * @returns {*} The value the was originally provided to
17420 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17421 * Otherwise, throws an exception.
17422 */
17423
17424 /**
17425 * @ngdoc method
17426 * @name $sce#getTrustedHtml
17427 *
17428 * @description
17429 * Shorthand method. `$sce.getTrustedHtml(value)` →
17430 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17431 *
17432 * @param {*} value The value to pass to `$sce.getTrusted`.
17433 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17434 */
17435
17436 /**
17437 * @ngdoc method
17438 * @name $sce#getTrustedCss
17439 *
17440 * @description
17441 * Shorthand method. `$sce.getTrustedCss(value)` →
17442 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17443 *
17444 * @param {*} value The value to pass to `$sce.getTrusted`.
17445 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17446 */
17447
17448 /**
17449 * @ngdoc method
17450 * @name $sce#getTrustedUrl
17451 *
17452 * @description
17453 * Shorthand method. `$sce.getTrustedUrl(value)` →
17454 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17455 *
17456 * @param {*} value The value to pass to `$sce.getTrusted`.
17457 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17458 */
17459
17460 /**
17461 * @ngdoc method
17462 * @name $sce#getTrustedResourceUrl
17463 *
17464 * @description
17465 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17466 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17467 *
17468 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17469 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17470 */
17471
17472 /**
17473 * @ngdoc method
17474 * @name $sce#getTrustedJs
17475 *
17476 * @description
17477 * Shorthand method. `$sce.getTrustedJs(value)` →
17478 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17479 *
17480 * @param {*} value The value to pass to `$sce.getTrusted`.
17481 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17482 */
17483
17484 /**
17485 * @ngdoc method
17486 * @name $sce#parseAsHtml
17487 *
17488 * @description
17489 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17490 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17491 *
17492 * @param {string} expression String expression to compile.
17493 * @returns {function(context, locals)} a function which represents the compiled expression:
17494 *
17495 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17496 * are evaluated against (typically a scope object).
17497 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17498 * `context`.
17499 */
17500
17501 /**
17502 * @ngdoc method
17503 * @name $sce#parseAsCss
17504 *
17505 * @description
17506 * Shorthand method. `$sce.parseAsCss(value)` →
17507 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17508 *
17509 * @param {string} expression String expression to compile.
17510 * @returns {function(context, locals)} a function which represents the compiled expression:
17511 *
17512 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17513 * are evaluated against (typically a scope object).
17514 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17515 * `context`.
17516 */
17517
17518 /**
17519 * @ngdoc method
17520 * @name $sce#parseAsUrl
17521 *
17522 * @description
17523 * Shorthand method. `$sce.parseAsUrl(value)` →
17524 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17525 *
17526 * @param {string} expression String expression to compile.
17527 * @returns {function(context, locals)} a function which represents the compiled expression:
17528 *
17529 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17530 * are evaluated against (typically a scope object).
17531 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17532 * `context`.
17533 */
17534
17535 /**
17536 * @ngdoc method
17537 * @name $sce#parseAsResourceUrl
17538 *
17539 * @description
17540 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17541 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17542 *
17543 * @param {string} expression String expression to compile.
17544 * @returns {function(context, locals)} a function which represents the compiled expression:
17545 *
17546 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17547 * are evaluated against (typically a scope object).
17548 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17549 * `context`.
17550 */
17551
17552 /**
17553 * @ngdoc method
17554 * @name $sce#parseAsJs
17555 *
17556 * @description
17557 * Shorthand method. `$sce.parseAsJs(value)` →
17558 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17559 *
17560 * @param {string} expression String expression to compile.
17561 * @returns {function(context, locals)} a function which represents the compiled expression:
17562 *
17563 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17564 * are evaluated against (typically a scope object).
17565 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17566 * `context`.
17567 */
17568
17569 // Shorthand delegations.
17570 var parse = sce.parseAs,
17571 getTrusted = sce.getTrusted,
17572 trustAs = sce.trustAs;
17573
17574 forEach(SCE_CONTEXTS, function(enumValue, name) {
17575 var lName = lowercase(name);
17576 sce[camelCase("parse_as_" + lName)] = function(expr) {
17577 return parse(enumValue, expr);
17578 };
17579 sce[camelCase("get_trusted_" + lName)] = function(value) {
17580 return getTrusted(enumValue, value);
17581 };
17582 sce[camelCase("trust_as_" + lName)] = function(value) {
17583 return trustAs(enumValue, value);
17584 };
17585 });
17586
17587 return sce;
17588 }];
17589}
17590
17591/**
17592 * !!! This is an undocumented "private" service !!!
17593 *
17594 * @name $sniffer
17595 * @requires $window
17596 * @requires $document
17597 *
17598 * @property {boolean} history Does the browser support html5 history api ?
17599 * @property {boolean} transitions Does the browser support CSS transition events ?
17600 * @property {boolean} animations Does the browser support CSS animation events ?
17601 *
17602 * @description
17603 * This is very simple implementation of testing browser's features.
17604 */
17605function $SnifferProvider() {
17606 this.$get = ['$window', '$document', function($window, $document) {
17607 var eventSupport = {},
17608 android =
17609 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17610 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17611 document = $document[0] || {},
17612 vendorPrefix,
17613 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17614 bodyStyle = document.body && document.body.style,
17615 transitions = false,
17616 animations = false,
17617 match;
17618
17619 if (bodyStyle) {
17620 for (var prop in bodyStyle) {
17621 if (match = vendorRegex.exec(prop)) {
17622 vendorPrefix = match[0];
17623 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17624 break;
17625 }
17626 }
17627
17628 if (!vendorPrefix) {
17629 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17630 }
17631
17632 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17633 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17634
17635 if (android && (!transitions || !animations)) {
17636 transitions = isString(bodyStyle.webkitTransition);
17637 animations = isString(bodyStyle.webkitAnimation);
17638 }
17639 }
17640
17641
17642 return {
17643 // Android has history.pushState, but it does not update location correctly
17644 // so let's not use the history API at all.
17645 // http://code.google.com/p/android/issues/detail?id=17471
17646 // https://github.com/angular/angular.js/issues/904
17647
17648 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17649 // so let's not use the history API also
17650 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17651 // jshint -W018
17652 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17653 // jshint +W018
17654 hasEvent: function(event) {
17655 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17656 // it. In particular the event is not fired when backspace or delete key are pressed or
17657 // when cut operation is performed.
17658 // IE10+ implements 'input' event but it erroneously fires under various situations,
17659 // e.g. when placeholder changes, or a form is focused.
17660 if (event === 'input' && msie <= 11) return false;
17661
17662 if (isUndefined(eventSupport[event])) {
17663 var divElm = document.createElement('div');
17664 eventSupport[event] = 'on' + event in divElm;
17665 }
17666
17667 return eventSupport[event];
17668 },
17669 csp: csp(),
17670 vendorPrefix: vendorPrefix,
17671 transitions: transitions,
17672 animations: animations,
17673 android: android
17674 };
17675 }];
17676}
17677
17678var $compileMinErr = minErr('$compile');
17679
17680/**
17681 * @ngdoc service
17682 * @name $templateRequest
17683 *
17684 * @description
17685 * The `$templateRequest` service runs security checks then downloads the provided template using
17686 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17687 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17688 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17689 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17690 * when `tpl` is of type string and `$templateCache` has the matching entry.
17691 *
17692 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17693 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17694 *
17695 * @return {Promise} a promise for the HTTP response data of the given URL.
17696 *
17697 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17698 */
17699function $TemplateRequestProvider() {
17700 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17701 function handleRequestFn(tpl, ignoreRequestError) {
17702 handleRequestFn.totalPendingRequests++;
17703
17704 // We consider the template cache holds only trusted templates, so
17705 // there's no need to go through whitelisting again for keys that already
17706 // are included in there. This also makes Angular accept any script
17707 // directive, no matter its name. However, we still need to unwrap trusted
17708 // types.
17709 if (!isString(tpl) || !$templateCache.get(tpl)) {
17710 tpl = $sce.getTrustedResourceUrl(tpl);
17711 }
17712
17713 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17714
17715 if (isArray(transformResponse)) {
17716 transformResponse = transformResponse.filter(function(transformer) {
17717 return transformer !== defaultHttpResponseTransform;
17718 });
17719 } else if (transformResponse === defaultHttpResponseTransform) {
17720 transformResponse = null;
17721 }
17722
17723 var httpOptions = {
17724 cache: $templateCache,
17725 transformResponse: transformResponse
17726 };
17727
17728 return $http.get(tpl, httpOptions)
17729 ['finally'](function() {
17730 handleRequestFn.totalPendingRequests--;
17731 })
17732 .then(function(response) {
17733 $templateCache.put(tpl, response.data);
17734 return response.data;
17735 }, handleError);
17736
17737 function handleError(resp) {
17738 if (!ignoreRequestError) {
17739 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17740 tpl, resp.status, resp.statusText);
17741 }
17742 return $q.reject(resp);
17743 }
17744 }
17745
17746 handleRequestFn.totalPendingRequests = 0;
17747
17748 return handleRequestFn;
17749 }];
17750}
17751
17752function $$TestabilityProvider() {
17753 this.$get = ['$rootScope', '$browser', '$location',
17754 function($rootScope, $browser, $location) {
17755
17756 /**
17757 * @name $testability
17758 *
17759 * @description
17760 * The private $$testability service provides a collection of methods for use when debugging
17761 * or by automated test and debugging tools.
17762 */
17763 var testability = {};
17764
17765 /**
17766 * @name $$testability#findBindings
17767 *
17768 * @description
17769 * Returns an array of elements that are bound (via ng-bind or {{}})
17770 * to expressions matching the input.
17771 *
17772 * @param {Element} element The element root to search from.
17773 * @param {string} expression The binding expression to match.
17774 * @param {boolean} opt_exactMatch If true, only returns exact matches
17775 * for the expression. Filters and whitespace are ignored.
17776 */
17777 testability.findBindings = function(element, expression, opt_exactMatch) {
17778 var bindings = element.getElementsByClassName('ng-binding');
17779 var matches = [];
17780 forEach(bindings, function(binding) {
17781 var dataBinding = angular.element(binding).data('$binding');
17782 if (dataBinding) {
17783 forEach(dataBinding, function(bindingName) {
17784 if (opt_exactMatch) {
17785 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17786 if (matcher.test(bindingName)) {
17787 matches.push(binding);
17788 }
17789 } else {
17790 if (bindingName.indexOf(expression) != -1) {
17791 matches.push(binding);
17792 }
17793 }
17794 });
17795 }
17796 });
17797 return matches;
17798 };
17799
17800 /**
17801 * @name $$testability#findModels
17802 *
17803 * @description
17804 * Returns an array of elements that are two-way found via ng-model to
17805 * expressions matching the input.
17806 *
17807 * @param {Element} element The element root to search from.
17808 * @param {string} expression The model expression to match.
17809 * @param {boolean} opt_exactMatch If true, only returns exact matches
17810 * for the expression.
17811 */
17812 testability.findModels = function(element, expression, opt_exactMatch) {
17813 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17814 for (var p = 0; p < prefixes.length; ++p) {
17815 var attributeEquals = opt_exactMatch ? '=' : '*=';
17816 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17817 var elements = element.querySelectorAll(selector);
17818 if (elements.length) {
17819 return elements;
17820 }
17821 }
17822 };
17823
17824 /**
17825 * @name $$testability#getLocation
17826 *
17827 * @description
17828 * Shortcut for getting the location in a browser agnostic way. Returns
17829 * the path, search, and hash. (e.g. /path?a=b#hash)
17830 */
17831 testability.getLocation = function() {
17832 return $location.url();
17833 };
17834
17835 /**
17836 * @name $$testability#setLocation
17837 *
17838 * @description
17839 * Shortcut for navigating to a location without doing a full page reload.
17840 *
17841 * @param {string} url The location url (path, search and hash,
17842 * e.g. /path?a=b#hash) to go to.
17843 */
17844 testability.setLocation = function(url) {
17845 if (url !== $location.url()) {
17846 $location.url(url);
17847 $rootScope.$digest();
17848 }
17849 };
17850
17851 /**
17852 * @name $$testability#whenStable
17853 *
17854 * @description
17855 * Calls the callback when $timeout and $http requests are completed.
17856 *
17857 * @param {function} callback
17858 */
17859 testability.whenStable = function(callback) {
17860 $browser.notifyWhenNoOutstandingRequests(callback);
17861 };
17862
17863 return testability;
17864 }];
17865}
17866
17867function $TimeoutProvider() {
17868 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17869 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17870
17871 var deferreds = {};
17872
17873
17874 /**
17875 * @ngdoc service
17876 * @name $timeout
17877 *
17878 * @description
17879 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17880 * block and delegates any exceptions to
17881 * {@link ng.$exceptionHandler $exceptionHandler} service.
17882 *
17883 * The return value of calling `$timeout` is a promise, which will be resolved when
17884 * the delay has passed and the timeout function, if provided, is executed.
17885 *
17886 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17887 *
17888 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17889 * synchronously flush the queue of deferred functions.
17890 *
17891 * If you only want a promise that will be resolved after some specified delay
17892 * then you can call `$timeout` without the `fn` function.
17893 *
17894 * @param {function()=} fn A function, whose execution should be delayed.
17895 * @param {number=} [delay=0] Delay in milliseconds.
17896 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17897 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17898 * @param {...*=} Pass additional parameters to the executed function.
17899 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17900 * promise will be resolved with is the return value of the `fn` function.
17901 *
17902 */
17903 function timeout(fn, delay, invokeApply) {
17904 if (!isFunction(fn)) {
17905 invokeApply = delay;
17906 delay = fn;
17907 fn = noop;
17908 }
17909
17910 var args = sliceArgs(arguments, 3),
17911 skipApply = (isDefined(invokeApply) && !invokeApply),
17912 deferred = (skipApply ? $$q : $q).defer(),
17913 promise = deferred.promise,
17914 timeoutId;
17915
17916 timeoutId = $browser.defer(function() {
17917 try {
17918 deferred.resolve(fn.apply(null, args));
17919 } catch (e) {
17920 deferred.reject(e);
17921 $exceptionHandler(e);
17922 }
17923 finally {
17924 delete deferreds[promise.$$timeoutId];
17925 }
17926
17927 if (!skipApply) $rootScope.$apply();
17928 }, delay);
17929
17930 promise.$$timeoutId = timeoutId;
17931 deferreds[timeoutId] = deferred;
17932
17933 return promise;
17934 }
17935
17936
17937 /**
17938 * @ngdoc method
17939 * @name $timeout#cancel
17940 *
17941 * @description
17942 * Cancels a task associated with the `promise`. As a result of this, the promise will be
17943 * resolved with a rejection.
17944 *
17945 * @param {Promise=} promise Promise returned by the `$timeout` function.
17946 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
17947 * canceled.
17948 */
17949 timeout.cancel = function(promise) {
17950 if (promise && promise.$$timeoutId in deferreds) {
17951 deferreds[promise.$$timeoutId].reject('canceled');
17952 delete deferreds[promise.$$timeoutId];
17953 return $browser.defer.cancel(promise.$$timeoutId);
17954 }
17955 return false;
17956 };
17957
17958 return timeout;
17959 }];
17960}
17961
17962// NOTE: The usage of window and document instead of $window and $document here is
17963// deliberate. This service depends on the specific behavior of anchor nodes created by the
17964// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
17965// cause us to break tests. In addition, when the browser resolves a URL for XHR, it
17966// doesn't know about mocked locations and resolves URLs to the real document - which is
17967// exactly the behavior needed here. There is little value is mocking these out for this
17968// service.
17969var urlParsingNode = document.createElement("a");
17970var originUrl = urlResolve(window.location.href);
17971
17972
17973/**
17974 *
17975 * Implementation Notes for non-IE browsers
17976 * ----------------------------------------
17977 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
17978 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
17979 * URL will be resolved into an absolute URL in the context of the application document.
17980 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
17981 * properties are all populated to reflect the normalized URL. This approach has wide
17982 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
17983 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
17984 *
17985 * Implementation Notes for IE
17986 * ---------------------------
17987 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
17988 * browsers. However, the parsed components will not be set if the URL assigned did not specify
17989 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
17990 * work around that by performing the parsing in a 2nd step by taking a previously normalized
17991 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
17992 * properties such as protocol, hostname, port, etc.
17993 *
17994 * References:
17995 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
17996 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
17997 * http://url.spec.whatwg.org/#urlutils
17998 * https://github.com/angular/angular.js/pull/2902
17999 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18000 *
18001 * @kind function
18002 * @param {string} url The URL to be parsed.
18003 * @description Normalizes and parses a URL.
18004 * @returns {object} Returns the normalized URL as a dictionary.
18005 *
18006 * | member name | Description |
18007 * |---------------|----------------|
18008 * | href | A normalized version of the provided URL if it was not an absolute URL |
18009 * | protocol | The protocol including the trailing colon |
18010 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18011 * | search | The search params, minus the question mark |
18012 * | hash | The hash string, minus the hash symbol
18013 * | hostname | The hostname
18014 * | port | The port, without ":"
18015 * | pathname | The pathname, beginning with "/"
18016 *
18017 */
18018function urlResolve(url) {
18019 var href = url;
18020
18021 if (msie) {
18022 // Normalize before parse. Refer Implementation Notes on why this is
18023 // done in two steps on IE.
18024 urlParsingNode.setAttribute("href", href);
18025 href = urlParsingNode.href;
18026 }
18027
18028 urlParsingNode.setAttribute('href', href);
18029
18030 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18031 return {
18032 href: urlParsingNode.href,
18033 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18034 host: urlParsingNode.host,
18035 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18036 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18037 hostname: urlParsingNode.hostname,
18038 port: urlParsingNode.port,
18039 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18040 ? urlParsingNode.pathname
18041 : '/' + urlParsingNode.pathname
18042 };
18043}
18044
18045/**
18046 * Parse a request URL and determine whether this is a same-origin request as the application document.
18047 *
18048 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18049 * or a parsed URL object.
18050 * @returns {boolean} Whether the request is for the same origin as the application document.
18051 */
18052function urlIsSameOrigin(requestUrl) {
18053 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18054 return (parsed.protocol === originUrl.protocol &&
18055 parsed.host === originUrl.host);
18056}
18057
18058/**
18059 * @ngdoc service
18060 * @name $window
18061 *
18062 * @description
18063 * A reference to the browser's `window` object. While `window`
18064 * is globally available in JavaScript, it causes testability problems, because
18065 * it is a global variable. In angular we always refer to it through the
18066 * `$window` service, so it may be overridden, removed or mocked for testing.
18067 *
18068 * Expressions, like the one defined for the `ngClick` directive in the example
18069 * below, are evaluated with respect to the current scope. Therefore, there is
18070 * no risk of inadvertently coding in a dependency on a global value in such an
18071 * expression.
18072 *
18073 * @example
18074 <example module="windowExample">
18075 <file name="index.html">
18076 <script>
18077 angular.module('windowExample', [])
18078 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18079 $scope.greeting = 'Hello, World!';
18080 $scope.doGreeting = function(greeting) {
18081 $window.alert(greeting);
18082 };
18083 }]);
18084 </script>
18085 <div ng-controller="ExampleController">
18086 <input type="text" ng-model="greeting" aria-label="greeting" />
18087 <button ng-click="doGreeting(greeting)">ALERT</button>
18088 </div>
18089 </file>
18090 <file name="protractor.js" type="protractor">
18091 it('should display the greeting in the input box', function() {
18092 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18093 // If we click the button it will block the test runner
18094 // element(':button').click();
18095 });
18096 </file>
18097 </example>
18098 */
18099function $WindowProvider() {
18100 this.$get = valueFn(window);
18101}
18102
18103/**
18104 * @name $$cookieReader
18105 * @requires $document
18106 *
18107 * @description
18108 * This is a private service for reading cookies used by $http and ngCookies
18109 *
18110 * @return {Object} a key/value map of the current cookies
18111 */
18112function $$CookieReader($document) {
18113 var rawDocument = $document[0] || {};
18114 var lastCookies = {};
18115 var lastCookieString = '';
18116
18117 function safeDecodeURIComponent(str) {
18118 try {
18119 return decodeURIComponent(str);
18120 } catch (e) {
18121 return str;
18122 }
18123 }
18124
18125 return function() {
18126 var cookieArray, cookie, i, index, name;
18127 var currentCookieString = rawDocument.cookie || '';
18128
18129 if (currentCookieString !== lastCookieString) {
18130 lastCookieString = currentCookieString;
18131 cookieArray = lastCookieString.split('; ');
18132 lastCookies = {};
18133
18134 for (i = 0; i < cookieArray.length; i++) {
18135 cookie = cookieArray[i];
18136 index = cookie.indexOf('=');
18137 if (index > 0) { //ignore nameless cookies
18138 name = safeDecodeURIComponent(cookie.substring(0, index));
18139 // the first value that is seen for a cookie is the most
18140 // specific one. values for the same cookie name that
18141 // follow are for less specific paths.
18142 if (isUndefined(lastCookies[name])) {
18143 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18144 }
18145 }
18146 }
18147 }
18148 return lastCookies;
18149 };
18150}
18151
18152$$CookieReader.$inject = ['$document'];
18153
18154function $$CookieReaderProvider() {
18155 this.$get = $$CookieReader;
18156}
18157
18158/* global currencyFilter: true,
18159 dateFilter: true,
18160 filterFilter: true,
18161 jsonFilter: true,
18162 limitToFilter: true,
18163 lowercaseFilter: true,
18164 numberFilter: true,
18165 orderByFilter: true,
18166 uppercaseFilter: true,
18167 */
18168
18169/**
18170 * @ngdoc provider
18171 * @name $filterProvider
18172 * @description
18173 *
18174 * Filters are just functions which transform input to an output. However filters need to be
18175 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18176 * annotated with dependencies and is responsible for creating a filter function.
18177 *
18178 * <div class="alert alert-warning">
18179 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18180 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18181 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18182 * (`myapp_subsection_filterx`).
18183 * </div>
18184 *
18185 * ```js
18186 * // Filter registration
18187 * function MyModule($provide, $filterProvider) {
18188 * // create a service to demonstrate injection (not always needed)
18189 * $provide.value('greet', function(name){
18190 * return 'Hello ' + name + '!';
18191 * });
18192 *
18193 * // register a filter factory which uses the
18194 * // greet service to demonstrate DI.
18195 * $filterProvider.register('greet', function(greet){
18196 * // return the filter function which uses the greet service
18197 * // to generate salutation
18198 * return function(text) {
18199 * // filters need to be forgiving so check input validity
18200 * return text && greet(text) || text;
18201 * };
18202 * });
18203 * }
18204 * ```
18205 *
18206 * The filter function is registered with the `$injector` under the filter name suffix with
18207 * `Filter`.
18208 *
18209 * ```js
18210 * it('should be the same instance', inject(
18211 * function($filterProvider) {
18212 * $filterProvider.register('reverse', function(){
18213 * return ...;
18214 * });
18215 * },
18216 * function($filter, reverseFilter) {
18217 * expect($filter('reverse')).toBe(reverseFilter);
18218 * });
18219 * ```
18220 *
18221 *
18222 * For more information about how angular filters work, and how to create your own filters, see
18223 * {@link guide/filter Filters} in the Angular Developer Guide.
18224 */
18225
18226/**
18227 * @ngdoc service
18228 * @name $filter
18229 * @kind function
18230 * @description
18231 * Filters are used for formatting data displayed to the user.
18232 *
18233 * The general syntax in templates is as follows:
18234 *
18235 * {{ expression [| filter_name[:parameter_value] ... ] }}
18236 *
18237 * @param {String} name Name of the filter function to retrieve
18238 * @return {Function} the filter function
18239 * @example
18240 <example name="$filter" module="filterExample">
18241 <file name="index.html">
18242 <div ng-controller="MainCtrl">
18243 <h3>{{ originalText }}</h3>
18244 <h3>{{ filteredText }}</h3>
18245 </div>
18246 </file>
18247
18248 <file name="script.js">
18249 angular.module('filterExample', [])
18250 .controller('MainCtrl', function($scope, $filter) {
18251 $scope.originalText = 'hello';
18252 $scope.filteredText = $filter('uppercase')($scope.originalText);
18253 });
18254 </file>
18255 </example>
18256 */
18257$FilterProvider.$inject = ['$provide'];
18258function $FilterProvider($provide) {
18259 var suffix = 'Filter';
18260
18261 /**
18262 * @ngdoc method
18263 * @name $filterProvider#register
18264 * @param {string|Object} name Name of the filter function, or an object map of filters where
18265 * the keys are the filter names and the values are the filter factories.
18266 *
18267 * <div class="alert alert-warning">
18268 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18269 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18270 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18271 * (`myapp_subsection_filterx`).
18272 * </div>
18273 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18274 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18275 * of the registered filter instances.
18276 */
18277 function register(name, factory) {
18278 if (isObject(name)) {
18279 var filters = {};
18280 forEach(name, function(filter, key) {
18281 filters[key] = register(key, filter);
18282 });
18283 return filters;
18284 } else {
18285 return $provide.factory(name + suffix, factory);
18286 }
18287 }
18288 this.register = register;
18289
18290 this.$get = ['$injector', function($injector) {
18291 return function(name) {
18292 return $injector.get(name + suffix);
18293 };
18294 }];
18295
18296 ////////////////////////////////////////
18297
18298 /* global
18299 currencyFilter: false,
18300 dateFilter: false,
18301 filterFilter: false,
18302 jsonFilter: false,
18303 limitToFilter: false,
18304 lowercaseFilter: false,
18305 numberFilter: false,
18306 orderByFilter: false,
18307 uppercaseFilter: false,
18308 */
18309
18310 register('currency', currencyFilter);
18311 register('date', dateFilter);
18312 register('filter', filterFilter);
18313 register('json', jsonFilter);
18314 register('limitTo', limitToFilter);
18315 register('lowercase', lowercaseFilter);
18316 register('number', numberFilter);
18317 register('orderBy', orderByFilter);
18318 register('uppercase', uppercaseFilter);
18319}
18320
18321/**
18322 * @ngdoc filter
18323 * @name filter
18324 * @kind function
18325 *
18326 * @description
18327 * Selects a subset of items from `array` and returns it as a new array.
18328 *
18329 * @param {Array} array The source array.
18330 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18331 * `array`.
18332 *
18333 * Can be one of:
18334 *
18335 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18336 * objects with string properties in `array` that match this string will be returned. This also
18337 * applies to nested object properties.
18338 * The predicate can be negated by prefixing the string with `!`.
18339 *
18340 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18341 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18342 * which have property `name` containing "M" and property `phone` containing "1". A special
18343 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18344 * property of the object or its nested object properties. That's equivalent to the simple
18345 * substring match with a `string` as described above. The predicate can be negated by prefixing
18346 * the string with `!`.
18347 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18348 * not containing "M".
18349 *
18350 * Note that a named property will match properties on the same level only, while the special
18351 * `$` property will match properties on the same level or deeper. E.g. an array item like
18352 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18353 * **will** be matched by `{$: 'John'}`.
18354 *
18355 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18356 * The function is called for each element of the array, with the element, its index, and
18357 * the entire array itself as arguments.
18358 *
18359 * The final result is an array of those elements that the predicate returned true for.
18360 *
18361 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18362 * determining if the expected value (from the filter expression) and actual value (from
18363 * the object in the array) should be considered a match.
18364 *
18365 * Can be one of:
18366 *
18367 * - `function(actual, expected)`:
18368 * The function will be given the object value and the predicate value to compare and
18369 * should return true if both values should be considered equal.
18370 *
18371 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18372 * This is essentially strict comparison of expected and actual.
18373 *
18374 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18375 * insensitive way.
18376 *
18377 * Primitive values are converted to strings. Objects are not compared against primitives,
18378 * unless they have a custom `toString` method (e.g. `Date` objects).
18379 *
18380 * @example
18381 <example>
18382 <file name="index.html">
18383 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18384 {name:'Mary', phone:'800-BIG-MARY'},
18385 {name:'Mike', phone:'555-4321'},
18386 {name:'Adam', phone:'555-5678'},
18387 {name:'Julie', phone:'555-8765'},
18388 {name:'Juliette', phone:'555-5678'}]"></div>
18389
18390 <label>Search: <input ng-model="searchText"></label>
18391 <table id="searchTextResults">
18392 <tr><th>Name</th><th>Phone</th></tr>
18393 <tr ng-repeat="friend in friends | filter:searchText">
18394 <td>{{friend.name}}</td>
18395 <td>{{friend.phone}}</td>
18396 </tr>
18397 </table>
18398 <hr>
18399 <label>Any: <input ng-model="search.$"></label> <br>
18400 <label>Name only <input ng-model="search.name"></label><br>
18401 <label>Phone only <input ng-model="search.phone"></label><br>
18402 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18403 <table id="searchObjResults">
18404 <tr><th>Name</th><th>Phone</th></tr>
18405 <tr ng-repeat="friendObj in friends | filter:search:strict">
18406 <td>{{friendObj.name}}</td>
18407 <td>{{friendObj.phone}}</td>
18408 </tr>
18409 </table>
18410 </file>
18411 <file name="protractor.js" type="protractor">
18412 var expectFriendNames = function(expectedNames, key) {
18413 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18414 arr.forEach(function(wd, i) {
18415 expect(wd.getText()).toMatch(expectedNames[i]);
18416 });
18417 });
18418 };
18419
18420 it('should search across all fields when filtering with a string', function() {
18421 var searchText = element(by.model('searchText'));
18422 searchText.clear();
18423 searchText.sendKeys('m');
18424 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18425
18426 searchText.clear();
18427 searchText.sendKeys('76');
18428 expectFriendNames(['John', 'Julie'], 'friend');
18429 });
18430
18431 it('should search in specific fields when filtering with a predicate object', function() {
18432 var searchAny = element(by.model('search.$'));
18433 searchAny.clear();
18434 searchAny.sendKeys('i');
18435 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18436 });
18437 it('should use a equal comparison when comparator is true', function() {
18438 var searchName = element(by.model('search.name'));
18439 var strict = element(by.model('strict'));
18440 searchName.clear();
18441 searchName.sendKeys('Julie');
18442 strict.click();
18443 expectFriendNames(['Julie'], 'friendObj');
18444 });
18445 </file>
18446 </example>
18447 */
18448function filterFilter() {
18449 return function(array, expression, comparator) {
18450 if (!isArrayLike(array)) {
18451 if (array == null) {
18452 return array;
18453 } else {
18454 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18455 }
18456 }
18457
18458 var expressionType = getTypeForFilter(expression);
18459 var predicateFn;
18460 var matchAgainstAnyProp;
18461
18462 switch (expressionType) {
18463 case 'function':
18464 predicateFn = expression;
18465 break;
18466 case 'boolean':
18467 case 'null':
18468 case 'number':
18469 case 'string':
18470 matchAgainstAnyProp = true;
18471 //jshint -W086
18472 case 'object':
18473 //jshint +W086
18474 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18475 break;
18476 default:
18477 return array;
18478 }
18479
18480 return Array.prototype.filter.call(array, predicateFn);
18481 };
18482}
18483
18484// Helper functions for `filterFilter`
18485function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18486 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18487 var predicateFn;
18488
18489 if (comparator === true) {
18490 comparator = equals;
18491 } else if (!isFunction(comparator)) {
18492 comparator = function(actual, expected) {
18493 if (isUndefined(actual)) {
18494 // No substring matching against `undefined`
18495 return false;
18496 }
18497 if ((actual === null) || (expected === null)) {
18498 // No substring matching against `null`; only match against `null`
18499 return actual === expected;
18500 }
18501 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18502 // Should not compare primitives against objects, unless they have custom `toString` method
18503 return false;
18504 }
18505
18506 actual = lowercase('' + actual);
18507 expected = lowercase('' + expected);
18508 return actual.indexOf(expected) !== -1;
18509 };
18510 }
18511
18512 predicateFn = function(item) {
18513 if (shouldMatchPrimitives && !isObject(item)) {
18514 return deepCompare(item, expression.$, comparator, false);
18515 }
18516 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18517 };
18518
18519 return predicateFn;
18520}
18521
18522function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18523 var actualType = getTypeForFilter(actual);
18524 var expectedType = getTypeForFilter(expected);
18525
18526 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18527 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18528 } else if (isArray(actual)) {
18529 // In case `actual` is an array, consider it a match
18530 // if ANY of it's items matches `expected`
18531 return actual.some(function(item) {
18532 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18533 });
18534 }
18535
18536 switch (actualType) {
18537 case 'object':
18538 var key;
18539 if (matchAgainstAnyProp) {
18540 for (key in actual) {
18541 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18542 return true;
18543 }
18544 }
18545 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18546 } else if (expectedType === 'object') {
18547 for (key in expected) {
18548 var expectedVal = expected[key];
18549 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18550 continue;
18551 }
18552
18553 var matchAnyProperty = key === '$';
18554 var actualVal = matchAnyProperty ? actual : actual[key];
18555 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18556 return false;
18557 }
18558 }
18559 return true;
18560 } else {
18561 return comparator(actual, expected);
18562 }
18563 break;
18564 case 'function':
18565 return false;
18566 default:
18567 return comparator(actual, expected);
18568 }
18569}
18570
18571// Used for easily differentiating between `null` and actual `object`
18572function getTypeForFilter(val) {
18573 return (val === null) ? 'null' : typeof val;
18574}
18575
18576/**
18577 * @ngdoc filter
18578 * @name currency
18579 * @kind function
18580 *
18581 * @description
18582 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18583 * symbol for current locale is used.
18584 *
18585 * @param {number} amount Input to filter.
18586 * @param {string=} symbol Currency symbol or identifier to be displayed.
18587 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18588 * @returns {string} Formatted number.
18589 *
18590 *
18591 * @example
18592 <example module="currencyExample">
18593 <file name="index.html">
18594 <script>
18595 angular.module('currencyExample', [])
18596 .controller('ExampleController', ['$scope', function($scope) {
18597 $scope.amount = 1234.56;
18598 }]);
18599 </script>
18600 <div ng-controller="ExampleController">
18601 <input type="number" ng-model="amount" aria-label="amount"> <br>
18602 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18603 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18604 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18605 </div>
18606 </file>
18607 <file name="protractor.js" type="protractor">
18608 it('should init with 1234.56', function() {
18609 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18610 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18611 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18612 });
18613 it('should update', function() {
18614 if (browser.params.browser == 'safari') {
18615 // Safari does not understand the minus key. See
18616 // https://github.com/angular/protractor/issues/481
18617 return;
18618 }
18619 element(by.model('amount')).clear();
18620 element(by.model('amount')).sendKeys('-1234');
18621 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18622 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18623 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18624 });
18625 </file>
18626 </example>
18627 */
18628currencyFilter.$inject = ['$locale'];
18629function currencyFilter($locale) {
18630 var formats = $locale.NUMBER_FORMATS;
18631 return function(amount, currencySymbol, fractionSize) {
18632 if (isUndefined(currencySymbol)) {
18633 currencySymbol = formats.CURRENCY_SYM;
18634 }
18635
18636 if (isUndefined(fractionSize)) {
18637 fractionSize = formats.PATTERNS[1].maxFrac;
18638 }
18639
18640 // if null or undefined pass it through
18641 return (amount == null)
18642 ? amount
18643 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18644 replace(/\u00A4/g, currencySymbol);
18645 };
18646}
18647
18648/**
18649 * @ngdoc filter
18650 * @name number
18651 * @kind function
18652 *
18653 * @description
18654 * Formats a number as text.
18655 *
18656 * If the input is null or undefined, it will just be returned.
18657 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18658 * If the input is not a number an empty string is returned.
18659 *
18660 *
18661 * @param {number|string} number Number to format.
18662 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18663 * If this is not provided then the fraction size is computed from the current locale's number
18664 * formatting pattern. In the case of the default locale, it will be 3.
18665 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18666 *
18667 * @example
18668 <example module="numberFilterExample">
18669 <file name="index.html">
18670 <script>
18671 angular.module('numberFilterExample', [])
18672 .controller('ExampleController', ['$scope', function($scope) {
18673 $scope.val = 1234.56789;
18674 }]);
18675 </script>
18676 <div ng-controller="ExampleController">
18677 <label>Enter number: <input ng-model='val'></label><br>
18678 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18679 No fractions: <span>{{val | number:0}}</span><br>
18680 Negative number: <span>{{-val | number:4}}</span>
18681 </div>
18682 </file>
18683 <file name="protractor.js" type="protractor">
18684 it('should format numbers', function() {
18685 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18686 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18687 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18688 });
18689
18690 it('should update', function() {
18691 element(by.model('val')).clear();
18692 element(by.model('val')).sendKeys('3374.333');
18693 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18694 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18695 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18696 });
18697 </file>
18698 </example>
18699 */
18700
18701
18702numberFilter.$inject = ['$locale'];
18703function numberFilter($locale) {
18704 var formats = $locale.NUMBER_FORMATS;
18705 return function(number, fractionSize) {
18706
18707 // if null or undefined pass it through
18708 return (number == null)
18709 ? number
18710 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18711 fractionSize);
18712 };
18713}
18714
18715var DECIMAL_SEP = '.';
18716function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18717 if (isObject(number)) return '';
18718
18719 var isNegative = number < 0;
18720 number = Math.abs(number);
18721
18722 var isInfinity = number === Infinity;
18723 if (!isInfinity && !isFinite(number)) return '';
18724
18725 var numStr = number + '',
18726 formatedText = '',
18727 hasExponent = false,
18728 parts = [];
18729
18730 if (isInfinity) formatedText = '\u221e';
18731
18732 if (!isInfinity && numStr.indexOf('e') !== -1) {
18733 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18734 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18735 number = 0;
18736 } else {
18737 formatedText = numStr;
18738 hasExponent = true;
18739 }
18740 }
18741
18742 if (!isInfinity && !hasExponent) {
18743 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18744
18745 // determine fractionSize if it is not specified
18746 if (isUndefined(fractionSize)) {
18747 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18748 }
18749
18750 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18751 // inspired by:
18752 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18753 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18754
18755 var fraction = ('' + number).split(DECIMAL_SEP);
18756 var whole = fraction[0];
18757 fraction = fraction[1] || '';
18758
18759 var i, pos = 0,
18760 lgroup = pattern.lgSize,
18761 group = pattern.gSize;
18762
18763 if (whole.length >= (lgroup + group)) {
18764 pos = whole.length - lgroup;
18765 for (i = 0; i < pos; i++) {
18766 if ((pos - i) % group === 0 && i !== 0) {
18767 formatedText += groupSep;
18768 }
18769 formatedText += whole.charAt(i);
18770 }
18771 }
18772
18773 for (i = pos; i < whole.length; i++) {
18774 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18775 formatedText += groupSep;
18776 }
18777 formatedText += whole.charAt(i);
18778 }
18779
18780 // format fraction part.
18781 while (fraction.length < fractionSize) {
18782 fraction += '0';
18783 }
18784
18785 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18786 } else {
18787 if (fractionSize > 0 && number < 1) {
18788 formatedText = number.toFixed(fractionSize);
18789 number = parseFloat(formatedText);
18790 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18791 }
18792 }
18793
18794 if (number === 0) {
18795 isNegative = false;
18796 }
18797
18798 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18799 formatedText,
18800 isNegative ? pattern.negSuf : pattern.posSuf);
18801 return parts.join('');
18802}
18803
18804function padNumber(num, digits, trim) {
18805 var neg = '';
18806 if (num < 0) {
18807 neg = '-';
18808 num = -num;
18809 }
18810 num = '' + num;
18811 while (num.length < digits) num = '0' + num;
18812 if (trim) {
18813 num = num.substr(num.length - digits);
18814 }
18815 return neg + num;
18816}
18817
18818
18819function dateGetter(name, size, offset, trim) {
18820 offset = offset || 0;
18821 return function(date) {
18822 var value = date['get' + name]();
18823 if (offset > 0 || value > -offset) {
18824 value += offset;
18825 }
18826 if (value === 0 && offset == -12) value = 12;
18827 return padNumber(value, size, trim);
18828 };
18829}
18830
18831function dateStrGetter(name, shortForm) {
18832 return function(date, formats) {
18833 var value = date['get' + name]();
18834 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18835
18836 return formats[get][value];
18837 };
18838}
18839
18840function timeZoneGetter(date, formats, offset) {
18841 var zone = -1 * offset;
18842 var paddedZone = (zone >= 0) ? "+" : "";
18843
18844 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18845 padNumber(Math.abs(zone % 60), 2);
18846
18847 return paddedZone;
18848}
18849
18850function getFirstThursdayOfYear(year) {
18851 // 0 = index of January
18852 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18853 // 4 = index of Thursday (+1 to account for 1st = 5)
18854 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18855 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18856}
18857
18858function getThursdayThisWeek(datetime) {
18859 return new Date(datetime.getFullYear(), datetime.getMonth(),
18860 // 4 = index of Thursday
18861 datetime.getDate() + (4 - datetime.getDay()));
18862}
18863
18864function weekGetter(size) {
18865 return function(date) {
18866 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18867 thisThurs = getThursdayThisWeek(date);
18868
18869 var diff = +thisThurs - +firstThurs,
18870 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18871
18872 return padNumber(result, size);
18873 };
18874}
18875
18876function ampmGetter(date, formats) {
18877 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18878}
18879
18880function eraGetter(date, formats) {
18881 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18882}
18883
18884function longEraGetter(date, formats) {
18885 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18886}
18887
18888var DATE_FORMATS = {
18889 yyyy: dateGetter('FullYear', 4),
18890 yy: dateGetter('FullYear', 2, 0, true),
18891 y: dateGetter('FullYear', 1),
18892 MMMM: dateStrGetter('Month'),
18893 MMM: dateStrGetter('Month', true),
18894 MM: dateGetter('Month', 2, 1),
18895 M: dateGetter('Month', 1, 1),
18896 dd: dateGetter('Date', 2),
18897 d: dateGetter('Date', 1),
18898 HH: dateGetter('Hours', 2),
18899 H: dateGetter('Hours', 1),
18900 hh: dateGetter('Hours', 2, -12),
18901 h: dateGetter('Hours', 1, -12),
18902 mm: dateGetter('Minutes', 2),
18903 m: dateGetter('Minutes', 1),
18904 ss: dateGetter('Seconds', 2),
18905 s: dateGetter('Seconds', 1),
18906 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18907 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18908 sss: dateGetter('Milliseconds', 3),
18909 EEEE: dateStrGetter('Day'),
18910 EEE: dateStrGetter('Day', true),
18911 a: ampmGetter,
18912 Z: timeZoneGetter,
18913 ww: weekGetter(2),
18914 w: weekGetter(1),
18915 G: eraGetter,
18916 GG: eraGetter,
18917 GGG: eraGetter,
18918 GGGG: longEraGetter
18919};
18920
18921var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
18922 NUMBER_STRING = /^\-?\d+$/;
18923
18924/**
18925 * @ngdoc filter
18926 * @name date
18927 * @kind function
18928 *
18929 * @description
18930 * Formats `date` to a string based on the requested `format`.
18931 *
18932 * `format` string can be composed of the following elements:
18933 *
18934 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
18935 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
18936 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
18937 * * `'MMMM'`: Month in year (January-December)
18938 * * `'MMM'`: Month in year (Jan-Dec)
18939 * * `'MM'`: Month in year, padded (01-12)
18940 * * `'M'`: Month in year (1-12)
18941 * * `'dd'`: Day in month, padded (01-31)
18942 * * `'d'`: Day in month (1-31)
18943 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
18944 * * `'EEE'`: Day in Week, (Sun-Sat)
18945 * * `'HH'`: Hour in day, padded (00-23)
18946 * * `'H'`: Hour in day (0-23)
18947 * * `'hh'`: Hour in AM/PM, padded (01-12)
18948 * * `'h'`: Hour in AM/PM, (1-12)
18949 * * `'mm'`: Minute in hour, padded (00-59)
18950 * * `'m'`: Minute in hour (0-59)
18951 * * `'ss'`: Second in minute, padded (00-59)
18952 * * `'s'`: Second in minute (0-59)
18953 * * `'sss'`: Millisecond in second, padded (000-999)
18954 * * `'a'`: AM/PM marker
18955 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
18956 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
18957 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
18958 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
18959 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
18960 *
18961 * `format` string can also be one of the following predefined
18962 * {@link guide/i18n localizable formats}:
18963 *
18964 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
18965 * (e.g. Sep 3, 2010 12:05:08 PM)
18966 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
18967 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
18968 * (e.g. Friday, September 3, 2010)
18969 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
18970 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
18971 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
18972 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
18973 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
18974 *
18975 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
18976 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
18977 * (e.g. `"h 'o''clock'"`).
18978 *
18979 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
18980 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
18981 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
18982 * specified in the string input, the time is considered to be in the local timezone.
18983 * @param {string=} format Formatting rules (see Description). If not specified,
18984 * `mediumDate` is used.
18985 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
18986 * continental US time zone abbreviations, but for general use, use a time zone offset, for
18987 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
18988 * If not specified, the timezone of the browser will be used.
18989 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
18990 *
18991 * @example
18992 <example>
18993 <file name="index.html">
18994 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
18995 <span>{{1288323623006 | date:'medium'}}</span><br>
18996 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
18997 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
18998 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
18999 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19000 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19001 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19002 </file>
19003 <file name="protractor.js" type="protractor">
19004 it('should format date', function() {
19005 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19006 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19007 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19008 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19009 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19010 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19011 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19012 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19013 });
19014 </file>
19015 </example>
19016 */
19017dateFilter.$inject = ['$locale'];
19018function dateFilter($locale) {
19019
19020
19021 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19022 // 1 2 3 4 5 6 7 8 9 10 11
19023 function jsonStringToDate(string) {
19024 var match;
19025 if (match = string.match(R_ISO8601_STR)) {
19026 var date = new Date(0),
19027 tzHour = 0,
19028 tzMin = 0,
19029 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19030 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19031
19032 if (match[9]) {
19033 tzHour = toInt(match[9] + match[10]);
19034 tzMin = toInt(match[9] + match[11]);
19035 }
19036 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19037 var h = toInt(match[4] || 0) - tzHour;
19038 var m = toInt(match[5] || 0) - tzMin;
19039 var s = toInt(match[6] || 0);
19040 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19041 timeSetter.call(date, h, m, s, ms);
19042 return date;
19043 }
19044 return string;
19045 }
19046
19047
19048 return function(date, format, timezone) {
19049 var text = '',
19050 parts = [],
19051 fn, match;
19052
19053 format = format || 'mediumDate';
19054 format = $locale.DATETIME_FORMATS[format] || format;
19055 if (isString(date)) {
19056 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19057 }
19058
19059 if (isNumber(date)) {
19060 date = new Date(date);
19061 }
19062
19063 if (!isDate(date) || !isFinite(date.getTime())) {
19064 return date;
19065 }
19066
19067 while (format) {
19068 match = DATE_FORMATS_SPLIT.exec(format);
19069 if (match) {
19070 parts = concat(parts, match, 1);
19071 format = parts.pop();
19072 } else {
19073 parts.push(format);
19074 format = null;
19075 }
19076 }
19077
19078 var dateTimezoneOffset = date.getTimezoneOffset();
19079 if (timezone) {
19080 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19081 date = convertTimezoneToLocal(date, timezone, true);
19082 }
19083 forEach(parts, function(value) {
19084 fn = DATE_FORMATS[value];
19085 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19086 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19087 });
19088
19089 return text;
19090 };
19091}
19092
19093
19094/**
19095 * @ngdoc filter
19096 * @name json
19097 * @kind function
19098 *
19099 * @description
19100 * Allows you to convert a JavaScript object into JSON string.
19101 *
19102 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19103 * the binding is automatically converted to JSON.
19104 *
19105 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19106 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19107 * @returns {string} JSON string.
19108 *
19109 *
19110 * @example
19111 <example>
19112 <file name="index.html">
19113 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19114 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19115 </file>
19116 <file name="protractor.js" type="protractor">
19117 it('should jsonify filtered objects', function() {
19118 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19119 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19120 });
19121 </file>
19122 </example>
19123 *
19124 */
19125function jsonFilter() {
19126 return function(object, spacing) {
19127 if (isUndefined(spacing)) {
19128 spacing = 2;
19129 }
19130 return toJson(object, spacing);
19131 };
19132}
19133
19134
19135/**
19136 * @ngdoc filter
19137 * @name lowercase
19138 * @kind function
19139 * @description
19140 * Converts string to lowercase.
19141 * @see angular.lowercase
19142 */
19143var lowercaseFilter = valueFn(lowercase);
19144
19145
19146/**
19147 * @ngdoc filter
19148 * @name uppercase
19149 * @kind function
19150 * @description
19151 * Converts string to uppercase.
19152 * @see angular.uppercase
19153 */
19154var uppercaseFilter = valueFn(uppercase);
19155
19156/**
19157 * @ngdoc filter
19158 * @name limitTo
19159 * @kind function
19160 *
19161 * @description
19162 * Creates a new array or string containing only a specified number of elements. The elements
19163 * are taken from either the beginning or the end of the source array, string or number, as specified by
19164 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19165 * converted to a string.
19166 *
19167 * @param {Array|string|number} input Source array, string or number to be limited.
19168 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19169 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19170 * If the number is negative, `limit` number of items from the end of the source array/string
19171 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19172 * the input will be returned unchanged.
19173 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19174 * indicates an offset from the end of `input`. Defaults to `0`.
19175 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19176 * had less than `limit` elements.
19177 *
19178 * @example
19179 <example module="limitToExample">
19180 <file name="index.html">
19181 <script>
19182 angular.module('limitToExample', [])
19183 .controller('ExampleController', ['$scope', function($scope) {
19184 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19185 $scope.letters = "abcdefghi";
19186 $scope.longNumber = 2345432342;
19187 $scope.numLimit = 3;
19188 $scope.letterLimit = 3;
19189 $scope.longNumberLimit = 3;
19190 }]);
19191 </script>
19192 <div ng-controller="ExampleController">
19193 <label>
19194 Limit {{numbers}} to:
19195 <input type="number" step="1" ng-model="numLimit">
19196 </label>
19197 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19198 <label>
19199 Limit {{letters}} to:
19200 <input type="number" step="1" ng-model="letterLimit">
19201 </label>
19202 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19203 <label>
19204 Limit {{longNumber}} to:
19205 <input type="number" step="1" ng-model="longNumberLimit">
19206 </label>
19207 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19208 </div>
19209 </file>
19210 <file name="protractor.js" type="protractor">
19211 var numLimitInput = element(by.model('numLimit'));
19212 var letterLimitInput = element(by.model('letterLimit'));
19213 var longNumberLimitInput = element(by.model('longNumberLimit'));
19214 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19215 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19216 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19217
19218 it('should limit the number array to first three items', function() {
19219 expect(numLimitInput.getAttribute('value')).toBe('3');
19220 expect(letterLimitInput.getAttribute('value')).toBe('3');
19221 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19222 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19223 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19224 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19225 });
19226
19227 // There is a bug in safari and protractor that doesn't like the minus key
19228 // it('should update the output when -3 is entered', function() {
19229 // numLimitInput.clear();
19230 // numLimitInput.sendKeys('-3');
19231 // letterLimitInput.clear();
19232 // letterLimitInput.sendKeys('-3');
19233 // longNumberLimitInput.clear();
19234 // longNumberLimitInput.sendKeys('-3');
19235 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19236 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19237 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19238 // });
19239
19240 it('should not exceed the maximum size of input array', function() {
19241 numLimitInput.clear();
19242 numLimitInput.sendKeys('100');
19243 letterLimitInput.clear();
19244 letterLimitInput.sendKeys('100');
19245 longNumberLimitInput.clear();
19246 longNumberLimitInput.sendKeys('100');
19247 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19248 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19249 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19250 });
19251 </file>
19252 </example>
19253*/
19254function limitToFilter() {
19255 return function(input, limit, begin) {
19256 if (Math.abs(Number(limit)) === Infinity) {
19257 limit = Number(limit);
19258 } else {
19259 limit = toInt(limit);
19260 }
19261 if (isNaN(limit)) return input;
19262
19263 if (isNumber(input)) input = input.toString();
19264 if (!isArray(input) && !isString(input)) return input;
19265
19266 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19267 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19268
19269 if (limit >= 0) {
19270 return input.slice(begin, begin + limit);
19271 } else {
19272 if (begin === 0) {
19273 return input.slice(limit, input.length);
19274 } else {
19275 return input.slice(Math.max(0, begin + limit), begin);
19276 }
19277 }
19278 };
19279}
19280
19281/**
19282 * @ngdoc filter
19283 * @name orderBy
19284 * @kind function
19285 *
19286 * @description
19287 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19288 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19289 * as expected, make sure they are actually being saved as numbers and not strings.
19290 *
19291 * @param {Array} array The array to sort.
19292 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19293 * used by the comparator to determine the order of elements.
19294 *
19295 * Can be one of:
19296 *
19297 * - `function`: Getter function. The result of this function will be sorted using the
19298 * `<`, `===`, `>` operator.
19299 * - `string`: An Angular expression. The result of this expression is used to compare elements
19300 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19301 * 3 first characters of a property called `name`). The result of a constant expression
19302 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19303 * to sort object by the value of their `special name` property). An expression can be
19304 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19305 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19306 * element itself is used to compare where sorting.
19307 * - `Array`: An array of function or string predicates. The first predicate in the array
19308 * is used for sorting, but when two items are equivalent, the next predicate is used.
19309 *
19310 * If the predicate is missing or empty then it defaults to `'+'`.
19311 *
19312 * @param {boolean=} reverse Reverse the order of the array.
19313 * @returns {Array} Sorted copy of the source array.
19314 *
19315 *
19316 * @example
19317 * The example below demonstrates a simple ngRepeat, where the data is sorted
19318 * by age in descending order (predicate is set to `'-age'`).
19319 * `reverse` is not set, which means it defaults to `false`.
19320 <example module="orderByExample">
19321 <file name="index.html">
19322 <script>
19323 angular.module('orderByExample', [])
19324 .controller('ExampleController', ['$scope', function($scope) {
19325 $scope.friends =
19326 [{name:'John', phone:'555-1212', age:10},
19327 {name:'Mary', phone:'555-9876', age:19},
19328 {name:'Mike', phone:'555-4321', age:21},
19329 {name:'Adam', phone:'555-5678', age:35},
19330 {name:'Julie', phone:'555-8765', age:29}];
19331 }]);
19332 </script>
19333 <div ng-controller="ExampleController">
19334 <table class="friend">
19335 <tr>
19336 <th>Name</th>
19337 <th>Phone Number</th>
19338 <th>Age</th>
19339 </tr>
19340 <tr ng-repeat="friend in friends | orderBy:'-age'">
19341 <td>{{friend.name}}</td>
19342 <td>{{friend.phone}}</td>
19343 <td>{{friend.age}}</td>
19344 </tr>
19345 </table>
19346 </div>
19347 </file>
19348 </example>
19349 *
19350 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19351 * as shown in the next example.
19352 * @example
19353 <example module="orderByExample">
19354 <file name="index.html">
19355 <script>
19356 angular.module('orderByExample', [])
19357 .controller('ExampleController', ['$scope', function($scope) {
19358 $scope.friends =
19359 [{name:'John', phone:'555-1212', age:10},
19360 {name:'Mary', phone:'555-9876', age:19},
19361 {name:'Mike', phone:'555-4321', age:21},
19362 {name:'Adam', phone:'555-5678', age:35},
19363 {name:'Julie', phone:'555-8765', age:29}];
19364 $scope.predicate = 'age';
19365 $scope.reverse = true;
19366 $scope.order = function(predicate) {
19367 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19368 $scope.predicate = predicate;
19369 };
19370 }]);
19371 </script>
19372 <style type="text/css">
19373 .sortorder:after {
19374 content: '\25b2';
19375 }
19376 .sortorder.reverse:after {
19377 content: '\25bc';
19378 }
19379 </style>
19380 <div ng-controller="ExampleController">
19381 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19382 <hr/>
19383 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19384 <table class="friend">
19385 <tr>
19386 <th>
19387 <a href="" ng-click="order('name')">Name</a>
19388 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19389 </th>
19390 <th>
19391 <a href="" ng-click="order('phone')">Phone Number</a>
19392 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19393 </th>
19394 <th>
19395 <a href="" ng-click="order('age')">Age</a>
19396 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19397 </th>
19398 </tr>
19399 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19400 <td>{{friend.name}}</td>
19401 <td>{{friend.phone}}</td>
19402 <td>{{friend.age}}</td>
19403 </tr>
19404 </table>
19405 </div>
19406 </file>
19407 </example>
19408 *
19409 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19410 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19411 * desired parameters.
19412 *
19413 * Example:
19414 *
19415 * @example
19416 <example module="orderByExample">
19417 <file name="index.html">
19418 <div ng-controller="ExampleController">
19419 <table class="friend">
19420 <tr>
19421 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19422 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19423 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19424 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19425 </tr>
19426 <tr ng-repeat="friend in friends">
19427 <td>{{friend.name}}</td>
19428 <td>{{friend.phone}}</td>
19429 <td>{{friend.age}}</td>
19430 </tr>
19431 </table>
19432 </div>
19433 </file>
19434
19435 <file name="script.js">
19436 angular.module('orderByExample', [])
19437 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19438 var orderBy = $filter('orderBy');
19439 $scope.friends = [
19440 { name: 'John', phone: '555-1212', age: 10 },
19441 { name: 'Mary', phone: '555-9876', age: 19 },
19442 { name: 'Mike', phone: '555-4321', age: 21 },
19443 { name: 'Adam', phone: '555-5678', age: 35 },
19444 { name: 'Julie', phone: '555-8765', age: 29 }
19445 ];
19446 $scope.order = function(predicate, reverse) {
19447 $scope.friends = orderBy($scope.friends, predicate, reverse);
19448 };
19449 $scope.order('-age',false);
19450 }]);
19451 </file>
19452</example>
19453 */
19454orderByFilter.$inject = ['$parse'];
19455function orderByFilter($parse) {
19456 return function(array, sortPredicate, reverseOrder) {
19457
19458 if (!(isArrayLike(array))) return array;
19459
19460 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19461 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19462
19463 var predicates = processPredicates(sortPredicate, reverseOrder);
19464 // Add a predicate at the end that evaluates to the element index. This makes the
19465 // sort stable as it works as a tie-breaker when all the input predicates cannot
19466 // distinguish between two elements.
19467 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19468
19469 // The next three lines are a version of a Swartzian Transform idiom from Perl
19470 // (sometimes called the Decorate-Sort-Undecorate idiom)
19471 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19472 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19473 compareValues.sort(doComparison);
19474 array = compareValues.map(function(item) { return item.value; });
19475
19476 return array;
19477
19478 function getComparisonObject(value, index) {
19479 return {
19480 value: value,
19481 predicateValues: predicates.map(function(predicate) {
19482 return getPredicateValue(predicate.get(value), index);
19483 })
19484 };
19485 }
19486
19487 function doComparison(v1, v2) {
19488 var result = 0;
19489 for (var index=0, length = predicates.length; index < length; ++index) {
19490 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19491 if (result) break;
19492 }
19493 return result;
19494 }
19495 };
19496
19497 function processPredicates(sortPredicate, reverseOrder) {
19498 reverseOrder = reverseOrder ? -1 : 1;
19499 return sortPredicate.map(function(predicate) {
19500 var descending = 1, get = identity;
19501
19502 if (isFunction(predicate)) {
19503 get = predicate;
19504 } else if (isString(predicate)) {
19505 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19506 descending = predicate.charAt(0) == '-' ? -1 : 1;
19507 predicate = predicate.substring(1);
19508 }
19509 if (predicate !== '') {
19510 get = $parse(predicate);
19511 if (get.constant) {
19512 var key = get();
19513 get = function(value) { return value[key]; };
19514 }
19515 }
19516 }
19517 return { get: get, descending: descending * reverseOrder };
19518 });
19519 }
19520
19521 function isPrimitive(value) {
19522 switch (typeof value) {
19523 case 'number': /* falls through */
19524 case 'boolean': /* falls through */
19525 case 'string':
19526 return true;
19527 default:
19528 return false;
19529 }
19530 }
19531
19532 function objectValue(value, index) {
19533 // If `valueOf` is a valid function use that
19534 if (typeof value.valueOf === 'function') {
19535 value = value.valueOf();
19536 if (isPrimitive(value)) return value;
19537 }
19538 // If `toString` is a valid function and not the one from `Object.prototype` use that
19539 if (hasCustomToString(value)) {
19540 value = value.toString();
19541 if (isPrimitive(value)) return value;
19542 }
19543 // We have a basic object so we use the position of the object in the collection
19544 return index;
19545 }
19546
19547 function getPredicateValue(value, index) {
19548 var type = typeof value;
19549 if (value === null) {
19550 type = 'string';
19551 value = 'null';
19552 } else if (type === 'string') {
19553 value = value.toLowerCase();
19554 } else if (type === 'object') {
19555 value = objectValue(value, index);
19556 }
19557 return { value: value, type: type };
19558 }
19559
19560 function compare(v1, v2) {
19561 var result = 0;
19562 if (v1.type === v2.type) {
19563 if (v1.value !== v2.value) {
19564 result = v1.value < v2.value ? -1 : 1;
19565 }
19566 } else {
19567 result = v1.type < v2.type ? -1 : 1;
19568 }
19569 return result;
19570 }
19571}
19572
19573function ngDirective(directive) {
19574 if (isFunction(directive)) {
19575 directive = {
19576 link: directive
19577 };
19578 }
19579 directive.restrict = directive.restrict || 'AC';
19580 return valueFn(directive);
19581}
19582
19583/**
19584 * @ngdoc directive
19585 * @name a
19586 * @restrict E
19587 *
19588 * @description
19589 * Modifies the default behavior of the html A tag so that the default action is prevented when
19590 * the href attribute is empty.
19591 *
19592 * This change permits the easy creation of action links with the `ngClick` directive
19593 * without changing the location or causing page reloads, e.g.:
19594 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19595 */
19596var htmlAnchorDirective = valueFn({
19597 restrict: 'E',
19598 compile: function(element, attr) {
19599 if (!attr.href && !attr.xlinkHref) {
19600 return function(scope, element) {
19601 // If the linked element is not an anchor tag anymore, do nothing
19602 if (element[0].nodeName.toLowerCase() !== 'a') return;
19603
19604 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19605 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19606 'xlink:href' : 'href';
19607 element.on('click', function(event) {
19608 // if we have no href url, then don't navigate anywhere.
19609 if (!element.attr(href)) {
19610 event.preventDefault();
19611 }
19612 });
19613 };
19614 }
19615 }
19616});
19617
19618/**
19619 * @ngdoc directive
19620 * @name ngHref
19621 * @restrict A
19622 * @priority 99
19623 *
19624 * @description
19625 * Using Angular markup like `{{hash}}` in an href attribute will
19626 * make the link go to the wrong URL if the user clicks it before
19627 * Angular has a chance to replace the `{{hash}}` markup with its
19628 * value. Until Angular replaces the markup the link will be broken
19629 * and will most likely return a 404 error. The `ngHref` directive
19630 * solves this problem.
19631 *
19632 * The wrong way to write it:
19633 * ```html
19634 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19635 * ```
19636 *
19637 * The correct way to write it:
19638 * ```html
19639 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19640 * ```
19641 *
19642 * @element A
19643 * @param {template} ngHref any string which can contain `{{}}` markup.
19644 *
19645 * @example
19646 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19647 * in links and their different behaviors:
19648 <example>
19649 <file name="index.html">
19650 <input ng-model="value" /><br />
19651 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19652 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19653 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19654 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19655 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19656 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19657 </file>
19658 <file name="protractor.js" type="protractor">
19659 it('should execute ng-click but not reload when href without value', function() {
19660 element(by.id('link-1')).click();
19661 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19662 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19663 });
19664
19665 it('should execute ng-click but not reload when href empty string', function() {
19666 element(by.id('link-2')).click();
19667 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19668 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19669 });
19670
19671 it('should execute ng-click and change url when ng-href specified', function() {
19672 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19673
19674 element(by.id('link-3')).click();
19675
19676 // At this point, we navigate away from an Angular page, so we need
19677 // to use browser.driver to get the base webdriver.
19678
19679 browser.wait(function() {
19680 return browser.driver.getCurrentUrl().then(function(url) {
19681 return url.match(/\/123$/);
19682 });
19683 }, 5000, 'page should navigate to /123');
19684 });
19685
19686 it('should execute ng-click but not reload when href empty string and name specified', function() {
19687 element(by.id('link-4')).click();
19688 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19689 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19690 });
19691
19692 it('should execute ng-click but not reload when no href but name specified', function() {
19693 element(by.id('link-5')).click();
19694 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19695 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19696 });
19697
19698 it('should only change url when only ng-href', function() {
19699 element(by.model('value')).clear();
19700 element(by.model('value')).sendKeys('6');
19701 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19702
19703 element(by.id('link-6')).click();
19704
19705 // At this point, we navigate away from an Angular page, so we need
19706 // to use browser.driver to get the base webdriver.
19707 browser.wait(function() {
19708 return browser.driver.getCurrentUrl().then(function(url) {
19709 return url.match(/\/6$/);
19710 });
19711 }, 5000, 'page should navigate to /6');
19712 });
19713 </file>
19714 </example>
19715 */
19716
19717/**
19718 * @ngdoc directive
19719 * @name ngSrc
19720 * @restrict A
19721 * @priority 99
19722 *
19723 * @description
19724 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19725 * work right: The browser will fetch from the URL with the literal
19726 * text `{{hash}}` until Angular replaces the expression inside
19727 * `{{hash}}`. The `ngSrc` directive solves this problem.
19728 *
19729 * The buggy way to write it:
19730 * ```html
19731 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19732 * ```
19733 *
19734 * The correct way to write it:
19735 * ```html
19736 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19737 * ```
19738 *
19739 * @element IMG
19740 * @param {template} ngSrc any string which can contain `{{}}` markup.
19741 */
19742
19743/**
19744 * @ngdoc directive
19745 * @name ngSrcset
19746 * @restrict A
19747 * @priority 99
19748 *
19749 * @description
19750 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19751 * work right: The browser will fetch from the URL with the literal
19752 * text `{{hash}}` until Angular replaces the expression inside
19753 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19754 *
19755 * The buggy way to write it:
19756 * ```html
19757 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19758 * ```
19759 *
19760 * The correct way to write it:
19761 * ```html
19762 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19763 * ```
19764 *
19765 * @element IMG
19766 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19767 */
19768
19769/**
19770 * @ngdoc directive
19771 * @name ngDisabled
19772 * @restrict A
19773 * @priority 100
19774 *
19775 * @description
19776 *
19777 * This directive sets the `disabled` attribute on the element if the
19778 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19779 *
19780 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19781 * attribute. The following example would make the button enabled on Chrome/Firefox
19782 * but not on older IEs:
19783 *
19784 * ```html
19785 * <!-- See below for an example of ng-disabled being used correctly -->
19786 * <div ng-init="isDisabled = false">
19787 * <button disabled="{{isDisabled}}">Disabled</button>
19788 * </div>
19789 * ```
19790 *
19791 * This is because the HTML specification does not require browsers to preserve the values of
19792 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19793 * If we put an Angular interpolation expression into such an attribute then the
19794 * binding information would be lost when the browser removes the attribute.
19795 *
19796 * @example
19797 <example>
19798 <file name="index.html">
19799 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19800 <button ng-model="button" ng-disabled="checked">Button</button>
19801 </file>
19802 <file name="protractor.js" type="protractor">
19803 it('should toggle button', function() {
19804 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19805 element(by.model('checked')).click();
19806 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19807 });
19808 </file>
19809 </example>
19810 *
19811 * @element INPUT
19812 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19813 * then the `disabled` attribute will be set on the element
19814 */
19815
19816
19817/**
19818 * @ngdoc directive
19819 * @name ngChecked
19820 * @restrict A
19821 * @priority 100
19822 *
19823 * @description
19824 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19825 *
19826 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19827 * as this can lead to unexpected behavior.
19828 *
19829 * ### Why do we need `ngChecked`?
19830 *
19831 * The HTML specification does not require browsers to preserve the values of boolean attributes
19832 * such as checked. (Their presence means true and their absence means false.)
19833 * If we put an Angular interpolation expression into such an attribute then the
19834 * binding information would be lost when the browser removes the attribute.
19835 * The `ngChecked` directive solves this problem for the `checked` attribute.
19836 * This complementary directive is not removed by the browser and so provides
19837 * a permanent reliable place to store the binding information.
19838 * @example
19839 <example>
19840 <file name="index.html">
19841 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19842 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19843 </file>
19844 <file name="protractor.js" type="protractor">
19845 it('should check both checkBoxes', function() {
19846 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19847 element(by.model('master')).click();
19848 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19849 });
19850 </file>
19851 </example>
19852 *
19853 * @element INPUT
19854 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19855 * then the `checked` attribute will be set on the element
19856 */
19857
19858
19859/**
19860 * @ngdoc directive
19861 * @name ngReadonly
19862 * @restrict A
19863 * @priority 100
19864 *
19865 * @description
19866 * The HTML specification does not require browsers to preserve the values of boolean attributes
19867 * such as readonly. (Their presence means true and their absence means false.)
19868 * If we put an Angular interpolation expression into such an attribute then the
19869 * binding information would be lost when the browser removes the attribute.
19870 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19871 * This complementary directive is not removed by the browser and so provides
19872 * a permanent reliable place to store the binding information.
19873 * @example
19874 <example>
19875 <file name="index.html">
19876 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19877 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19878 </file>
19879 <file name="protractor.js" type="protractor">
19880 it('should toggle readonly attr', function() {
19881 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19882 element(by.model('checked')).click();
19883 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19884 });
19885 </file>
19886 </example>
19887 *
19888 * @element INPUT
19889 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19890 * then special attribute "readonly" will be set on the element
19891 */
19892
19893
19894/**
19895 * @ngdoc directive
19896 * @name ngSelected
19897 * @restrict A
19898 * @priority 100
19899 *
19900 * @description
19901 * The HTML specification does not require browsers to preserve the values of boolean attributes
19902 * such as selected. (Their presence means true and their absence means false.)
19903 * If we put an Angular interpolation expression into such an attribute then the
19904 * binding information would be lost when the browser removes the attribute.
19905 * The `ngSelected` directive solves this problem for the `selected` attribute.
19906 * This complementary directive is not removed by the browser and so provides
19907 * a permanent reliable place to store the binding information.
19908 *
19909 * @example
19910 <example>
19911 <file name="index.html">
19912 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19913 <select aria-label="ngSelected demo">
19914 <option>Hello!</option>
19915 <option id="greet" ng-selected="selected">Greetings!</option>
19916 </select>
19917 </file>
19918 <file name="protractor.js" type="protractor">
19919 it('should select Greetings!', function() {
19920 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19921 element(by.model('selected')).click();
19922 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
19923 });
19924 </file>
19925 </example>
19926 *
19927 * @element OPTION
19928 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
19929 * then special attribute "selected" will be set on the element
19930 */
19931
19932/**
19933 * @ngdoc directive
19934 * @name ngOpen
19935 * @restrict A
19936 * @priority 100
19937 *
19938 * @description
19939 * The HTML specification does not require browsers to preserve the values of boolean attributes
19940 * such as open. (Their presence means true and their absence means false.)
19941 * If we put an Angular interpolation expression into such an attribute then the
19942 * binding information would be lost when the browser removes the attribute.
19943 * The `ngOpen` directive solves this problem for the `open` attribute.
19944 * This complementary directive is not removed by the browser and so provides
19945 * a permanent reliable place to store the binding information.
19946 * @example
19947 <example>
19948 <file name="index.html">
19949 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
19950 <details id="details" ng-open="open">
19951 <summary>Show/Hide me</summary>
19952 </details>
19953 </file>
19954 <file name="protractor.js" type="protractor">
19955 it('should toggle open', function() {
19956 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
19957 element(by.model('open')).click();
19958 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
19959 });
19960 </file>
19961 </example>
19962 *
19963 * @element DETAILS
19964 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
19965 * then special attribute "open" will be set on the element
19966 */
19967
19968var ngAttributeAliasDirectives = {};
19969
19970// boolean attrs are evaluated
19971forEach(BOOLEAN_ATTR, function(propName, attrName) {
19972 // binding to multiple is not supported
19973 if (propName == "multiple") return;
19974
19975 function defaultLinkFn(scope, element, attr) {
19976 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
19977 attr.$set(attrName, !!value);
19978 });
19979 }
19980
19981 var normalized = directiveNormalize('ng-' + attrName);
19982 var linkFn = defaultLinkFn;
19983
19984 if (propName === 'checked') {
19985 linkFn = function(scope, element, attr) {
19986 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
19987 if (attr.ngModel !== attr[normalized]) {
19988 defaultLinkFn(scope, element, attr);
19989 }
19990 };
19991 }
19992
19993 ngAttributeAliasDirectives[normalized] = function() {
19994 return {
19995 restrict: 'A',
19996 priority: 100,
19997 link: linkFn
19998 };
19999 };
20000});
20001
20002// aliased input attrs are evaluated
20003forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20004 ngAttributeAliasDirectives[ngAttr] = function() {
20005 return {
20006 priority: 100,
20007 link: function(scope, element, attr) {
20008 //special case ngPattern when a literal regular expression value
20009 //is used as the expression (this way we don't have to watch anything).
20010 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20011 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20012 if (match) {
20013 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20014 return;
20015 }
20016 }
20017
20018 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20019 attr.$set(ngAttr, value);
20020 });
20021 }
20022 };
20023 };
20024});
20025
20026// ng-src, ng-srcset, ng-href are interpolated
20027forEach(['src', 'srcset', 'href'], function(attrName) {
20028 var normalized = directiveNormalize('ng-' + attrName);
20029 ngAttributeAliasDirectives[normalized] = function() {
20030 return {
20031 priority: 99, // it needs to run after the attributes are interpolated
20032 link: function(scope, element, attr) {
20033 var propName = attrName,
20034 name = attrName;
20035
20036 if (attrName === 'href' &&
20037 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20038 name = 'xlinkHref';
20039 attr.$attr[name] = 'xlink:href';
20040 propName = null;
20041 }
20042
20043 attr.$observe(normalized, function(value) {
20044 if (!value) {
20045 if (attrName === 'href') {
20046 attr.$set(name, null);
20047 }
20048 return;
20049 }
20050
20051 attr.$set(name, value);
20052
20053 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20054 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20055 // to set the property as well to achieve the desired effect.
20056 // we use attr[attrName] value since $set can sanitize the url.
20057 if (msie && propName) element.prop(propName, attr[name]);
20058 });
20059 }
20060 };
20061 };
20062});
20063
20064/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20065 */
20066var nullFormCtrl = {
20067 $addControl: noop,
20068 $$renameControl: nullFormRenameControl,
20069 $removeControl: noop,
20070 $setValidity: noop,
20071 $setDirty: noop,
20072 $setPristine: noop,
20073 $setSubmitted: noop
20074},
20075SUBMITTED_CLASS = 'ng-submitted';
20076
20077function nullFormRenameControl(control, name) {
20078 control.$name = name;
20079}
20080
20081/**
20082 * @ngdoc type
20083 * @name form.FormController
20084 *
20085 * @property {boolean} $pristine True if user has not interacted with the form yet.
20086 * @property {boolean} $dirty True if user has already interacted with the form.
20087 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20088 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20089 * @property {boolean} $pending True if at least one containing control or form is pending.
20090 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20091 *
20092 * @property {Object} $error Is an object hash, containing references to controls or
20093 * forms with failing validators, where:
20094 *
20095 * - keys are validation tokens (error names),
20096 * - values are arrays of controls or forms that have a failing validator for given error name.
20097 *
20098 * Built-in validation tokens:
20099 *
20100 * - `email`
20101 * - `max`
20102 * - `maxlength`
20103 * - `min`
20104 * - `minlength`
20105 * - `number`
20106 * - `pattern`
20107 * - `required`
20108 * - `url`
20109 * - `date`
20110 * - `datetimelocal`
20111 * - `time`
20112 * - `week`
20113 * - `month`
20114 *
20115 * @description
20116 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20117 * such as being valid/invalid or dirty/pristine.
20118 *
20119 * Each {@link ng.directive:form form} directive creates an instance
20120 * of `FormController`.
20121 *
20122 */
20123//asks for $scope to fool the BC controller module
20124FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20125function FormController(element, attrs, $scope, $animate, $interpolate) {
20126 var form = this,
20127 controls = [];
20128
20129 // init state
20130 form.$error = {};
20131 form.$$success = {};
20132 form.$pending = undefined;
20133 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20134 form.$dirty = false;
20135 form.$pristine = true;
20136 form.$valid = true;
20137 form.$invalid = false;
20138 form.$submitted = false;
20139 form.$$parentForm = nullFormCtrl;
20140
20141 /**
20142 * @ngdoc method
20143 * @name form.FormController#$rollbackViewValue
20144 *
20145 * @description
20146 * Rollback all form controls pending updates to the `$modelValue`.
20147 *
20148 * Updates may be pending by a debounced event or because the input is waiting for a some future
20149 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20150 * a form that uses `ng-model-options` to pend updates.
20151 */
20152 form.$rollbackViewValue = function() {
20153 forEach(controls, function(control) {
20154 control.$rollbackViewValue();
20155 });
20156 };
20157
20158 /**
20159 * @ngdoc method
20160 * @name form.FormController#$commitViewValue
20161 *
20162 * @description
20163 * Commit all form controls pending updates to the `$modelValue`.
20164 *
20165 * Updates may be pending by a debounced event or because the input is waiting for a some future
20166 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20167 * usually handles calling this in response to input events.
20168 */
20169 form.$commitViewValue = function() {
20170 forEach(controls, function(control) {
20171 control.$commitViewValue();
20172 });
20173 };
20174
20175 /**
20176 * @ngdoc method
20177 * @name form.FormController#$addControl
20178 * @param {object} control control object, either a {@link form.FormController} or an
20179 * {@link ngModel.NgModelController}
20180 *
20181 * @description
20182 * Register a control with the form. Input elements using ngModelController do this automatically
20183 * when they are linked.
20184 *
20185 * Note that the current state of the control will not be reflected on the new parent form. This
20186 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20187 * state.
20188 *
20189 * However, if the method is used programmatically, for example by adding dynamically created controls,
20190 * or controls that have been previously removed without destroying their corresponding DOM element,
20191 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20192 *
20193 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20194 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20195 */
20196 form.$addControl = function(control) {
20197 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20198 // and not added to the scope. Now we throw an error.
20199 assertNotHasOwnProperty(control.$name, 'input');
20200 controls.push(control);
20201
20202 if (control.$name) {
20203 form[control.$name] = control;
20204 }
20205
20206 control.$$parentForm = form;
20207 };
20208
20209 // Private API: rename a form control
20210 form.$$renameControl = function(control, newName) {
20211 var oldName = control.$name;
20212
20213 if (form[oldName] === control) {
20214 delete form[oldName];
20215 }
20216 form[newName] = control;
20217 control.$name = newName;
20218 };
20219
20220 /**
20221 * @ngdoc method
20222 * @name form.FormController#$removeControl
20223 * @param {object} control control object, either a {@link form.FormController} or an
20224 * {@link ngModel.NgModelController}
20225 *
20226 * @description
20227 * Deregister a control from the form.
20228 *
20229 * Input elements using ngModelController do this automatically when they are destroyed.
20230 *
20231 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20232 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20233 * different from case to case. For example, removing the only `$dirty` control from a form may or
20234 * may not mean that the form is still `$dirty`.
20235 */
20236 form.$removeControl = function(control) {
20237 if (control.$name && form[control.$name] === control) {
20238 delete form[control.$name];
20239 }
20240 forEach(form.$pending, function(value, name) {
20241 form.$setValidity(name, null, control);
20242 });
20243 forEach(form.$error, function(value, name) {
20244 form.$setValidity(name, null, control);
20245 });
20246 forEach(form.$$success, function(value, name) {
20247 form.$setValidity(name, null, control);
20248 });
20249
20250 arrayRemove(controls, control);
20251 control.$$parentForm = nullFormCtrl;
20252 };
20253
20254
20255 /**
20256 * @ngdoc method
20257 * @name form.FormController#$setValidity
20258 *
20259 * @description
20260 * Sets the validity of a form control.
20261 *
20262 * This method will also propagate to parent forms.
20263 */
20264 addSetValidityMethod({
20265 ctrl: this,
20266 $element: element,
20267 set: function(object, property, controller) {
20268 var list = object[property];
20269 if (!list) {
20270 object[property] = [controller];
20271 } else {
20272 var index = list.indexOf(controller);
20273 if (index === -1) {
20274 list.push(controller);
20275 }
20276 }
20277 },
20278 unset: function(object, property, controller) {
20279 var list = object[property];
20280 if (!list) {
20281 return;
20282 }
20283 arrayRemove(list, controller);
20284 if (list.length === 0) {
20285 delete object[property];
20286 }
20287 },
20288 $animate: $animate
20289 });
20290
20291 /**
20292 * @ngdoc method
20293 * @name form.FormController#$setDirty
20294 *
20295 * @description
20296 * Sets the form to a dirty state.
20297 *
20298 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20299 * state (ng-dirty class). This method will also propagate to parent forms.
20300 */
20301 form.$setDirty = function() {
20302 $animate.removeClass(element, PRISTINE_CLASS);
20303 $animate.addClass(element, DIRTY_CLASS);
20304 form.$dirty = true;
20305 form.$pristine = false;
20306 form.$$parentForm.$setDirty();
20307 };
20308
20309 /**
20310 * @ngdoc method
20311 * @name form.FormController#$setPristine
20312 *
20313 * @description
20314 * Sets the form to its pristine state.
20315 *
20316 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20317 * state (ng-pristine class). This method will also propagate to all the controls contained
20318 * in this form.
20319 *
20320 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20321 * saving or resetting it.
20322 */
20323 form.$setPristine = function() {
20324 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20325 form.$dirty = false;
20326 form.$pristine = true;
20327 form.$submitted = false;
20328 forEach(controls, function(control) {
20329 control.$setPristine();
20330 });
20331 };
20332
20333 /**
20334 * @ngdoc method
20335 * @name form.FormController#$setUntouched
20336 *
20337 * @description
20338 * Sets the form to its untouched state.
20339 *
20340 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20341 * untouched state (ng-untouched class).
20342 *
20343 * Setting a form controls back to their untouched state is often useful when setting the form
20344 * back to its pristine state.
20345 */
20346 form.$setUntouched = function() {
20347 forEach(controls, function(control) {
20348 control.$setUntouched();
20349 });
20350 };
20351
20352 /**
20353 * @ngdoc method
20354 * @name form.FormController#$setSubmitted
20355 *
20356 * @description
20357 * Sets the form to its submitted state.
20358 */
20359 form.$setSubmitted = function() {
20360 $animate.addClass(element, SUBMITTED_CLASS);
20361 form.$submitted = true;
20362 form.$$parentForm.$setSubmitted();
20363 };
20364}
20365
20366/**
20367 * @ngdoc directive
20368 * @name ngForm
20369 * @restrict EAC
20370 *
20371 * @description
20372 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20373 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20374 * sub-group of controls needs to be determined.
20375 *
20376 * Note: the purpose of `ngForm` is to group controls,
20377 * but not to be a replacement for the `<form>` tag with all of its capabilities
20378 * (e.g. posting to the server, ...).
20379 *
20380 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20381 * related scope, under this name.
20382 *
20383 */
20384
20385 /**
20386 * @ngdoc directive
20387 * @name form
20388 * @restrict E
20389 *
20390 * @description
20391 * Directive that instantiates
20392 * {@link form.FormController FormController}.
20393 *
20394 * If the `name` attribute is specified, the form controller is published onto the current scope under
20395 * this name.
20396 *
20397 * # Alias: {@link ng.directive:ngForm `ngForm`}
20398 *
20399 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20400 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20401 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20402 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20403 * using Angular validation directives in forms that are dynamically generated using the
20404 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20405 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20406 * `ngForm` directive and nest these in an outer `form` element.
20407 *
20408 *
20409 * # CSS classes
20410 * - `ng-valid` is set if the form is valid.
20411 * - `ng-invalid` is set if the form is invalid.
20412 * - `ng-pending` is set if the form is pending.
20413 * - `ng-pristine` is set if the form is pristine.
20414 * - `ng-dirty` is set if the form is dirty.
20415 * - `ng-submitted` is set if the form was submitted.
20416 *
20417 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20418 *
20419 *
20420 * # Submitting a form and preventing the default action
20421 *
20422 * Since the role of forms in client-side Angular applications is different than in classical
20423 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20424 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20425 * to handle the form submission in an application-specific way.
20426 *
20427 * For this reason, Angular prevents the default action (form submission to the server) unless the
20428 * `<form>` element has an `action` attribute specified.
20429 *
20430 * You can use one of the following two ways to specify what javascript method should be called when
20431 * a form is submitted:
20432 *
20433 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20434 * - {@link ng.directive:ngClick ngClick} directive on the first
20435 * button or input field of type submit (input[type=submit])
20436 *
20437 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20438 * or {@link ng.directive:ngClick ngClick} directives.
20439 * This is because of the following form submission rules in the HTML specification:
20440 *
20441 * - If a form has only one input field then hitting enter in this field triggers form submit
20442 * (`ngSubmit`)
20443 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20444 * doesn't trigger submit
20445 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20446 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20447 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20448 *
20449 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20450 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20451 * to have access to the updated model.
20452 *
20453 * ## Animation Hooks
20454 *
20455 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20456 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20457 * other validations that are performed within the form. Animations in ngForm are similar to how
20458 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20459 * as JS animations.
20460 *
20461 * The following example shows a simple way to utilize CSS transitions to style a form element
20462 * that has been rendered as invalid after it has been validated:
20463 *
20464 * <pre>
20465 * //be sure to include ngAnimate as a module to hook into more
20466 * //advanced animations
20467 * .my-form {
20468 * transition:0.5s linear all;
20469 * background: white;
20470 * }
20471 * .my-form.ng-invalid {
20472 * background: red;
20473 * color:white;
20474 * }
20475 * </pre>
20476 *
20477 * @example
20478 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20479 <file name="index.html">
20480 <script>
20481 angular.module('formExample', [])
20482 .controller('FormController', ['$scope', function($scope) {
20483 $scope.userType = 'guest';
20484 }]);
20485 </script>
20486 <style>
20487 .my-form {
20488 transition:all linear 0.5s;
20489 background: transparent;
20490 }
20491 .my-form.ng-invalid {
20492 background: red;
20493 }
20494 </style>
20495 <form name="myForm" ng-controller="FormController" class="my-form">
20496 userType: <input name="input" ng-model="userType" required>
20497 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20498 <code>userType = {{userType}}</code><br>
20499 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20500 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20501 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20502 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20503 </form>
20504 </file>
20505 <file name="protractor.js" type="protractor">
20506 it('should initialize to model', function() {
20507 var userType = element(by.binding('userType'));
20508 var valid = element(by.binding('myForm.input.$valid'));
20509
20510 expect(userType.getText()).toContain('guest');
20511 expect(valid.getText()).toContain('true');
20512 });
20513
20514 it('should be invalid if empty', function() {
20515 var userType = element(by.binding('userType'));
20516 var valid = element(by.binding('myForm.input.$valid'));
20517 var userInput = element(by.model('userType'));
20518
20519 userInput.clear();
20520 userInput.sendKeys('');
20521
20522 expect(userType.getText()).toEqual('userType =');
20523 expect(valid.getText()).toContain('false');
20524 });
20525 </file>
20526 </example>
20527 *
20528 * @param {string=} name Name of the form. If specified, the form controller will be published into
20529 * related scope, under this name.
20530 */
20531var formDirectiveFactory = function(isNgForm) {
20532 return ['$timeout', '$parse', function($timeout, $parse) {
20533 var formDirective = {
20534 name: 'form',
20535 restrict: isNgForm ? 'EAC' : 'E',
20536 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20537 controller: FormController,
20538 compile: function ngFormCompile(formElement, attr) {
20539 // Setup initial state of the control
20540 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20541
20542 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20543
20544 return {
20545 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20546 var controller = ctrls[0];
20547
20548 // if `action` attr is not present on the form, prevent the default action (submission)
20549 if (!('action' in attr)) {
20550 // we can't use jq events because if a form is destroyed during submission the default
20551 // action is not prevented. see #1238
20552 //
20553 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20554 // page reload if the form was destroyed by submission of the form via a click handler
20555 // on a button in the form. Looks like an IE9 specific bug.
20556 var handleFormSubmission = function(event) {
20557 scope.$apply(function() {
20558 controller.$commitViewValue();
20559 controller.$setSubmitted();
20560 });
20561
20562 event.preventDefault();
20563 };
20564
20565 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20566
20567 // unregister the preventDefault listener so that we don't not leak memory but in a
20568 // way that will achieve the prevention of the default action.
20569 formElement.on('$destroy', function() {
20570 $timeout(function() {
20571 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20572 }, 0, false);
20573 });
20574 }
20575
20576 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20577 parentFormCtrl.$addControl(controller);
20578
20579 var setter = nameAttr ? getSetter(controller.$name) : noop;
20580
20581 if (nameAttr) {
20582 setter(scope, controller);
20583 attr.$observe(nameAttr, function(newValue) {
20584 if (controller.$name === newValue) return;
20585 setter(scope, undefined);
20586 controller.$$parentForm.$$renameControl(controller, newValue);
20587 setter = getSetter(controller.$name);
20588 setter(scope, controller);
20589 });
20590 }
20591 formElement.on('$destroy', function() {
20592 controller.$$parentForm.$removeControl(controller);
20593 setter(scope, undefined);
20594 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20595 });
20596 }
20597 };
20598 }
20599 };
20600
20601 return formDirective;
20602
20603 function getSetter(expression) {
20604 if (expression === '') {
20605 //create an assignable expression, so forms with an empty name can be renamed later
20606 return $parse('this[""]').assign;
20607 }
20608 return $parse(expression).assign || noop;
20609 }
20610 }];
20611};
20612
20613var formDirective = formDirectiveFactory();
20614var ngFormDirective = formDirectiveFactory(true);
20615
20616/* global VALID_CLASS: false,
20617 INVALID_CLASS: false,
20618 PRISTINE_CLASS: false,
20619 DIRTY_CLASS: false,
20620 UNTOUCHED_CLASS: false,
20621 TOUCHED_CLASS: false,
20622 ngModelMinErr: false,
20623*/
20624
20625// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20626var 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)/;
20627// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20628var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20629var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
20630var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20631var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20632var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20633var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20634var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20635var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20636
20637var inputType = {
20638
20639 /**
20640 * @ngdoc input
20641 * @name input[text]
20642 *
20643 * @description
20644 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20645 *
20646 *
20647 * @param {string} ngModel Assignable angular expression to data-bind to.
20648 * @param {string=} name Property name of the form under which the control is published.
20649 * @param {string=} required Adds `required` validation error key if the value is not entered.
20650 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20651 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20652 * `required` when you want to data-bind to the `required` attribute.
20653 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20654 * minlength.
20655 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20656 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20657 * any length.
20658 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20659 * that contains the regular expression body that will be converted to a regular expression
20660 * as in the ngPattern directive.
20661 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20662 * a RegExp found by evaluating the Angular expression given in the attribute value.
20663 * If the expression evaluates to a RegExp object, then this is used directly.
20664 * If the expression evaluates to a string, then it will be converted to a RegExp
20665 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20666 * `new RegExp('^abc$')`.<br />
20667 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20668 * start at the index of the last search's match, thus not taking the whole input value into
20669 * account.
20670 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20671 * interaction with the input element.
20672 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20673 * This parameter is ignored for input[type=password] controls, which will never trim the
20674 * input.
20675 *
20676 * @example
20677 <example name="text-input-directive" module="textInputExample">
20678 <file name="index.html">
20679 <script>
20680 angular.module('textInputExample', [])
20681 .controller('ExampleController', ['$scope', function($scope) {
20682 $scope.example = {
20683 text: 'guest',
20684 word: /^\s*\w*\s*$/
20685 };
20686 }]);
20687 </script>
20688 <form name="myForm" ng-controller="ExampleController">
20689 <label>Single word:
20690 <input type="text" name="input" ng-model="example.text"
20691 ng-pattern="example.word" required ng-trim="false">
20692 </label>
20693 <div role="alert">
20694 <span class="error" ng-show="myForm.input.$error.required">
20695 Required!</span>
20696 <span class="error" ng-show="myForm.input.$error.pattern">
20697 Single word only!</span>
20698 </div>
20699 <tt>text = {{example.text}}</tt><br/>
20700 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20701 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20702 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20703 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20704 </form>
20705 </file>
20706 <file name="protractor.js" type="protractor">
20707 var text = element(by.binding('example.text'));
20708 var valid = element(by.binding('myForm.input.$valid'));
20709 var input = element(by.model('example.text'));
20710
20711 it('should initialize to model', function() {
20712 expect(text.getText()).toContain('guest');
20713 expect(valid.getText()).toContain('true');
20714 });
20715
20716 it('should be invalid if empty', function() {
20717 input.clear();
20718 input.sendKeys('');
20719
20720 expect(text.getText()).toEqual('text =');
20721 expect(valid.getText()).toContain('false');
20722 });
20723
20724 it('should be invalid if multi word', function() {
20725 input.clear();
20726 input.sendKeys('hello world');
20727
20728 expect(valid.getText()).toContain('false');
20729 });
20730 </file>
20731 </example>
20732 */
20733 'text': textInputType,
20734
20735 /**
20736 * @ngdoc input
20737 * @name input[date]
20738 *
20739 * @description
20740 * Input with date validation and transformation. In browsers that do not yet support
20741 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20742 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20743 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20744 * expected input format via a placeholder or label.
20745 *
20746 * The model must always be a Date object, otherwise Angular will throw an error.
20747 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20748 *
20749 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20750 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20751 *
20752 * @param {string} ngModel Assignable angular expression to data-bind to.
20753 * @param {string=} name Property name of the form under which the control is published.
20754 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20755 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20756 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20757 * constraint validation.
20758 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20759 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20760 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20761 * constraint validation.
20762 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20763 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20764 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20765 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20766 * @param {string=} required Sets `required` validation error key if the value is not entered.
20767 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20768 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20769 * `required` when you want to data-bind to the `required` attribute.
20770 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20771 * interaction with the input element.
20772 *
20773 * @example
20774 <example name="date-input-directive" module="dateInputExample">
20775 <file name="index.html">
20776 <script>
20777 angular.module('dateInputExample', [])
20778 .controller('DateController', ['$scope', function($scope) {
20779 $scope.example = {
20780 value: new Date(2013, 9, 22)
20781 };
20782 }]);
20783 </script>
20784 <form name="myForm" ng-controller="DateController as dateCtrl">
20785 <label for="exampleInput">Pick a date in 2013:</label>
20786 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20787 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20788 <div role="alert">
20789 <span class="error" ng-show="myForm.input.$error.required">
20790 Required!</span>
20791 <span class="error" ng-show="myForm.input.$error.date">
20792 Not a valid date!</span>
20793 </div>
20794 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20795 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20796 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20797 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20798 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20799 </form>
20800 </file>
20801 <file name="protractor.js" type="protractor">
20802 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20803 var valid = element(by.binding('myForm.input.$valid'));
20804 var input = element(by.model('example.value'));
20805
20806 // currently protractor/webdriver does not support
20807 // sending keys to all known HTML5 input controls
20808 // for various browsers (see https://github.com/angular/protractor/issues/562).
20809 function setInput(val) {
20810 // set the value of the element and force validation.
20811 var scr = "var ipt = document.getElementById('exampleInput'); " +
20812 "ipt.value = '" + val + "';" +
20813 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20814 browser.executeScript(scr);
20815 }
20816
20817 it('should initialize to model', function() {
20818 expect(value.getText()).toContain('2013-10-22');
20819 expect(valid.getText()).toContain('myForm.input.$valid = true');
20820 });
20821
20822 it('should be invalid if empty', function() {
20823 setInput('');
20824 expect(value.getText()).toEqual('value =');
20825 expect(valid.getText()).toContain('myForm.input.$valid = false');
20826 });
20827
20828 it('should be invalid if over max', function() {
20829 setInput('2015-01-01');
20830 expect(value.getText()).toContain('');
20831 expect(valid.getText()).toContain('myForm.input.$valid = false');
20832 });
20833 </file>
20834 </example>
20835 */
20836 'date': createDateInputType('date', DATE_REGEXP,
20837 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20838 'yyyy-MM-dd'),
20839
20840 /**
20841 * @ngdoc input
20842 * @name input[datetime-local]
20843 *
20844 * @description
20845 * Input with datetime validation and transformation. In browsers that do not yet support
20846 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20847 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20848 *
20849 * The model must always be a Date object, otherwise Angular will throw an error.
20850 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20851 *
20852 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20853 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20854 *
20855 * @param {string} ngModel Assignable angular expression to data-bind to.
20856 * @param {string=} name Property name of the form under which the control is published.
20857 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20858 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20859 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20860 * Note that `min` will also add native HTML5 constraint validation.
20861 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20862 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20863 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20864 * Note that `max` will also add native HTML5 constraint validation.
20865 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20866 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20867 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20868 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20869 * @param {string=} required Sets `required` validation error key if the value is not entered.
20870 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20871 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20872 * `required` when you want to data-bind to the `required` attribute.
20873 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20874 * interaction with the input element.
20875 *
20876 * @example
20877 <example name="datetimelocal-input-directive" module="dateExample">
20878 <file name="index.html">
20879 <script>
20880 angular.module('dateExample', [])
20881 .controller('DateController', ['$scope', function($scope) {
20882 $scope.example = {
20883 value: new Date(2010, 11, 28, 14, 57)
20884 };
20885 }]);
20886 </script>
20887 <form name="myForm" ng-controller="DateController as dateCtrl">
20888 <label for="exampleInput">Pick a date between in 2013:</label>
20889 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20890 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20891 <div role="alert">
20892 <span class="error" ng-show="myForm.input.$error.required">
20893 Required!</span>
20894 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20895 Not a valid date!</span>
20896 </div>
20897 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20898 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20899 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20900 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20901 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20902 </form>
20903 </file>
20904 <file name="protractor.js" type="protractor">
20905 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20906 var valid = element(by.binding('myForm.input.$valid'));
20907 var input = element(by.model('example.value'));
20908
20909 // currently protractor/webdriver does not support
20910 // sending keys to all known HTML5 input controls
20911 // for various browsers (https://github.com/angular/protractor/issues/562).
20912 function setInput(val) {
20913 // set the value of the element and force validation.
20914 var scr = "var ipt = document.getElementById('exampleInput'); " +
20915 "ipt.value = '" + val + "';" +
20916 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20917 browser.executeScript(scr);
20918 }
20919
20920 it('should initialize to model', function() {
20921 expect(value.getText()).toContain('2010-12-28T14:57:00');
20922 expect(valid.getText()).toContain('myForm.input.$valid = true');
20923 });
20924
20925 it('should be invalid if empty', function() {
20926 setInput('');
20927 expect(value.getText()).toEqual('value =');
20928 expect(valid.getText()).toContain('myForm.input.$valid = false');
20929 });
20930
20931 it('should be invalid if over max', function() {
20932 setInput('2015-01-01T23:59:00');
20933 expect(value.getText()).toContain('');
20934 expect(valid.getText()).toContain('myForm.input.$valid = false');
20935 });
20936 </file>
20937 </example>
20938 */
20939 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
20940 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
20941 'yyyy-MM-ddTHH:mm:ss.sss'),
20942
20943 /**
20944 * @ngdoc input
20945 * @name input[time]
20946 *
20947 * @description
20948 * Input with time validation and transformation. In browsers that do not yet support
20949 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20950 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
20951 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
20952 *
20953 * The model must always be a Date object, otherwise Angular will throw an error.
20954 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20955 *
20956 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20957 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20958 *
20959 * @param {string} ngModel Assignable angular expression to data-bind to.
20960 * @param {string=} name Property name of the form under which the control is published.
20961 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20962 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
20963 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
20964 * native HTML5 constraint validation.
20965 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20966 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
20967 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
20968 * native HTML5 constraint validation.
20969 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
20970 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20971 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
20972 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20973 * @param {string=} required Sets `required` validation error key if the value is not entered.
20974 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20975 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20976 * `required` when you want to data-bind to the `required` attribute.
20977 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20978 * interaction with the input element.
20979 *
20980 * @example
20981 <example name="time-input-directive" module="timeExample">
20982 <file name="index.html">
20983 <script>
20984 angular.module('timeExample', [])
20985 .controller('DateController', ['$scope', function($scope) {
20986 $scope.example = {
20987 value: new Date(1970, 0, 1, 14, 57, 0)
20988 };
20989 }]);
20990 </script>
20991 <form name="myForm" ng-controller="DateController as dateCtrl">
20992 <label for="exampleInput">Pick a between 8am and 5pm:</label>
20993 <input type="time" id="exampleInput" name="input" ng-model="example.value"
20994 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
20995 <div role="alert">
20996 <span class="error" ng-show="myForm.input.$error.required">
20997 Required!</span>
20998 <span class="error" ng-show="myForm.input.$error.time">
20999 Not a valid date!</span>
21000 </div>
21001 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21002 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21003 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21004 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21005 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21006 </form>
21007 </file>
21008 <file name="protractor.js" type="protractor">
21009 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21010 var valid = element(by.binding('myForm.input.$valid'));
21011 var input = element(by.model('example.value'));
21012
21013 // currently protractor/webdriver does not support
21014 // sending keys to all known HTML5 input controls
21015 // for various browsers (https://github.com/angular/protractor/issues/562).
21016 function setInput(val) {
21017 // set the value of the element and force validation.
21018 var scr = "var ipt = document.getElementById('exampleInput'); " +
21019 "ipt.value = '" + val + "';" +
21020 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21021 browser.executeScript(scr);
21022 }
21023
21024 it('should initialize to model', function() {
21025 expect(value.getText()).toContain('14:57:00');
21026 expect(valid.getText()).toContain('myForm.input.$valid = true');
21027 });
21028
21029 it('should be invalid if empty', function() {
21030 setInput('');
21031 expect(value.getText()).toEqual('value =');
21032 expect(valid.getText()).toContain('myForm.input.$valid = false');
21033 });
21034
21035 it('should be invalid if over max', function() {
21036 setInput('23:59:00');
21037 expect(value.getText()).toContain('');
21038 expect(valid.getText()).toContain('myForm.input.$valid = false');
21039 });
21040 </file>
21041 </example>
21042 */
21043 'time': createDateInputType('time', TIME_REGEXP,
21044 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21045 'HH:mm:ss.sss'),
21046
21047 /**
21048 * @ngdoc input
21049 * @name input[week]
21050 *
21051 * @description
21052 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21053 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21054 * week format (yyyy-W##), for example: `2013-W02`.
21055 *
21056 * The model must always be a Date object, otherwise Angular will throw an error.
21057 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21058 *
21059 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21060 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21061 *
21062 * @param {string} ngModel Assignable angular expression to data-bind to.
21063 * @param {string=} name Property name of the form under which the control is published.
21064 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21065 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21066 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21067 * native HTML5 constraint validation.
21068 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21069 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21070 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21071 * native HTML5 constraint validation.
21072 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21073 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21074 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21075 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21076 * @param {string=} required Sets `required` validation error key if the value is not entered.
21077 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21078 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21079 * `required` when you want to data-bind to the `required` attribute.
21080 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21081 * interaction with the input element.
21082 *
21083 * @example
21084 <example name="week-input-directive" module="weekExample">
21085 <file name="index.html">
21086 <script>
21087 angular.module('weekExample', [])
21088 .controller('DateController', ['$scope', function($scope) {
21089 $scope.example = {
21090 value: new Date(2013, 0, 3)
21091 };
21092 }]);
21093 </script>
21094 <form name="myForm" ng-controller="DateController as dateCtrl">
21095 <label>Pick a date between in 2013:
21096 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21097 placeholder="YYYY-W##" min="2012-W32"
21098 max="2013-W52" required />
21099 </label>
21100 <div role="alert">
21101 <span class="error" ng-show="myForm.input.$error.required">
21102 Required!</span>
21103 <span class="error" ng-show="myForm.input.$error.week">
21104 Not a valid date!</span>
21105 </div>
21106 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21107 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21108 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21109 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21110 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21111 </form>
21112 </file>
21113 <file name="protractor.js" type="protractor">
21114 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21115 var valid = element(by.binding('myForm.input.$valid'));
21116 var input = element(by.model('example.value'));
21117
21118 // currently protractor/webdriver does not support
21119 // sending keys to all known HTML5 input controls
21120 // for various browsers (https://github.com/angular/protractor/issues/562).
21121 function setInput(val) {
21122 // set the value of the element and force validation.
21123 var scr = "var ipt = document.getElementById('exampleInput'); " +
21124 "ipt.value = '" + val + "';" +
21125 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21126 browser.executeScript(scr);
21127 }
21128
21129 it('should initialize to model', function() {
21130 expect(value.getText()).toContain('2013-W01');
21131 expect(valid.getText()).toContain('myForm.input.$valid = true');
21132 });
21133
21134 it('should be invalid if empty', function() {
21135 setInput('');
21136 expect(value.getText()).toEqual('value =');
21137 expect(valid.getText()).toContain('myForm.input.$valid = false');
21138 });
21139
21140 it('should be invalid if over max', function() {
21141 setInput('2015-W01');
21142 expect(value.getText()).toContain('');
21143 expect(valid.getText()).toContain('myForm.input.$valid = false');
21144 });
21145 </file>
21146 </example>
21147 */
21148 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21149
21150 /**
21151 * @ngdoc input
21152 * @name input[month]
21153 *
21154 * @description
21155 * Input with month validation and transformation. In browsers that do not yet support
21156 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21157 * month format (yyyy-MM), for example: `2009-01`.
21158 *
21159 * The model must always be a Date object, otherwise Angular will throw an error.
21160 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21161 * If the model is not set to the first of the month, the next view to model update will set it
21162 * to the first of the month.
21163 *
21164 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21165 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21166 *
21167 * @param {string} ngModel Assignable angular expression to data-bind to.
21168 * @param {string=} name Property name of the form under which the control is published.
21169 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21170 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21171 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21172 * native HTML5 constraint validation.
21173 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21174 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21175 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21176 * native HTML5 constraint validation.
21177 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21178 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21179 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21180 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21181
21182 * @param {string=} required Sets `required` validation error key if the value is not entered.
21183 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21184 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21185 * `required` when you want to data-bind to the `required` attribute.
21186 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21187 * interaction with the input element.
21188 *
21189 * @example
21190 <example name="month-input-directive" module="monthExample">
21191 <file name="index.html">
21192 <script>
21193 angular.module('monthExample', [])
21194 .controller('DateController', ['$scope', function($scope) {
21195 $scope.example = {
21196 value: new Date(2013, 9, 1)
21197 };
21198 }]);
21199 </script>
21200 <form name="myForm" ng-controller="DateController as dateCtrl">
21201 <label for="exampleInput">Pick a month in 2013:</label>
21202 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21203 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21204 <div role="alert">
21205 <span class="error" ng-show="myForm.input.$error.required">
21206 Required!</span>
21207 <span class="error" ng-show="myForm.input.$error.month">
21208 Not a valid month!</span>
21209 </div>
21210 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21211 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21212 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21213 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21214 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21215 </form>
21216 </file>
21217 <file name="protractor.js" type="protractor">
21218 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21219 var valid = element(by.binding('myForm.input.$valid'));
21220 var input = element(by.model('example.value'));
21221
21222 // currently protractor/webdriver does not support
21223 // sending keys to all known HTML5 input controls
21224 // for various browsers (https://github.com/angular/protractor/issues/562).
21225 function setInput(val) {
21226 // set the value of the element and force validation.
21227 var scr = "var ipt = document.getElementById('exampleInput'); " +
21228 "ipt.value = '" + val + "';" +
21229 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21230 browser.executeScript(scr);
21231 }
21232
21233 it('should initialize to model', function() {
21234 expect(value.getText()).toContain('2013-10');
21235 expect(valid.getText()).toContain('myForm.input.$valid = true');
21236 });
21237
21238 it('should be invalid if empty', function() {
21239 setInput('');
21240 expect(value.getText()).toEqual('value =');
21241 expect(valid.getText()).toContain('myForm.input.$valid = false');
21242 });
21243
21244 it('should be invalid if over max', function() {
21245 setInput('2015-01');
21246 expect(value.getText()).toContain('');
21247 expect(valid.getText()).toContain('myForm.input.$valid = false');
21248 });
21249 </file>
21250 </example>
21251 */
21252 'month': createDateInputType('month', MONTH_REGEXP,
21253 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21254 'yyyy-MM'),
21255
21256 /**
21257 * @ngdoc input
21258 * @name input[number]
21259 *
21260 * @description
21261 * Text input with number validation and transformation. Sets the `number` validation
21262 * error if not a valid number.
21263 *
21264 * <div class="alert alert-warning">
21265 * The model must always be of type `number` otherwise Angular will throw an error.
21266 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21267 * error docs for more information and an example of how to convert your model if necessary.
21268 * </div>
21269 *
21270 * ## Issues with HTML5 constraint validation
21271 *
21272 * In browsers that follow the
21273 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21274 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21275 * If a non-number is entered in the input, the browser will report the value as an empty string,
21276 * which means the view / model values in `ngModel` and subsequently the scope value
21277 * will also be an empty string.
21278 *
21279 *
21280 * @param {string} ngModel Assignable angular expression to data-bind to.
21281 * @param {string=} name Property name of the form under which the control is published.
21282 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21283 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21284 * @param {string=} required Sets `required` validation error key if the value is not entered.
21285 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21286 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21287 * `required` when you want to data-bind to the `required` attribute.
21288 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21289 * minlength.
21290 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21291 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21292 * any length.
21293 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21294 * that contains the regular expression body that will be converted to a regular expression
21295 * as in the ngPattern directive.
21296 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21297 * a RegExp found by evaluating the Angular expression given in the attribute value.
21298 * If the expression evaluates to a RegExp object, then this is used directly.
21299 * If the expression evaluates to a string, then it will be converted to a RegExp
21300 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21301 * `new RegExp('^abc$')`.<br />
21302 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21303 * start at the index of the last search's match, thus not taking the whole input value into
21304 * account.
21305 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21306 * interaction with the input element.
21307 *
21308 * @example
21309 <example name="number-input-directive" module="numberExample">
21310 <file name="index.html">
21311 <script>
21312 angular.module('numberExample', [])
21313 .controller('ExampleController', ['$scope', function($scope) {
21314 $scope.example = {
21315 value: 12
21316 };
21317 }]);
21318 </script>
21319 <form name="myForm" ng-controller="ExampleController">
21320 <label>Number:
21321 <input type="number" name="input" ng-model="example.value"
21322 min="0" max="99" required>
21323 </label>
21324 <div role="alert">
21325 <span class="error" ng-show="myForm.input.$error.required">
21326 Required!</span>
21327 <span class="error" ng-show="myForm.input.$error.number">
21328 Not valid number!</span>
21329 </div>
21330 <tt>value = {{example.value}}</tt><br/>
21331 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21332 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21333 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21334 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21335 </form>
21336 </file>
21337 <file name="protractor.js" type="protractor">
21338 var value = element(by.binding('example.value'));
21339 var valid = element(by.binding('myForm.input.$valid'));
21340 var input = element(by.model('example.value'));
21341
21342 it('should initialize to model', function() {
21343 expect(value.getText()).toContain('12');
21344 expect(valid.getText()).toContain('true');
21345 });
21346
21347 it('should be invalid if empty', function() {
21348 input.clear();
21349 input.sendKeys('');
21350 expect(value.getText()).toEqual('value =');
21351 expect(valid.getText()).toContain('false');
21352 });
21353
21354 it('should be invalid if over max', function() {
21355 input.clear();
21356 input.sendKeys('123');
21357 expect(value.getText()).toEqual('value =');
21358 expect(valid.getText()).toContain('false');
21359 });
21360 </file>
21361 </example>
21362 */
21363 'number': numberInputType,
21364
21365
21366 /**
21367 * @ngdoc input
21368 * @name input[url]
21369 *
21370 * @description
21371 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21372 * valid URL.
21373 *
21374 * <div class="alert alert-warning">
21375 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21376 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21377 * the built-in validators (see the {@link guide/forms Forms guide})
21378 * </div>
21379 *
21380 * @param {string} ngModel Assignable angular expression to data-bind to.
21381 * @param {string=} name Property name of the form under which the control is published.
21382 * @param {string=} required Sets `required` validation error key if the value is not entered.
21383 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21384 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21385 * `required` when you want to data-bind to the `required` attribute.
21386 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21387 * minlength.
21388 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21389 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21390 * any length.
21391 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21392 * that contains the regular expression body that will be converted to a regular expression
21393 * as in the ngPattern directive.
21394 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21395 * a RegExp found by evaluating the Angular expression given in the attribute value.
21396 * If the expression evaluates to a RegExp object, then this is used directly.
21397 * If the expression evaluates to a string, then it will be converted to a RegExp
21398 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21399 * `new RegExp('^abc$')`.<br />
21400 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21401 * start at the index of the last search's match, thus not taking the whole input value into
21402 * account.
21403 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21404 * interaction with the input element.
21405 *
21406 * @example
21407 <example name="url-input-directive" module="urlExample">
21408 <file name="index.html">
21409 <script>
21410 angular.module('urlExample', [])
21411 .controller('ExampleController', ['$scope', function($scope) {
21412 $scope.url = {
21413 text: 'http://google.com'
21414 };
21415 }]);
21416 </script>
21417 <form name="myForm" ng-controller="ExampleController">
21418 <label>URL:
21419 <input type="url" name="input" ng-model="url.text" required>
21420 <label>
21421 <div role="alert">
21422 <span class="error" ng-show="myForm.input.$error.required">
21423 Required!</span>
21424 <span class="error" ng-show="myForm.input.$error.url">
21425 Not valid url!</span>
21426 </div>
21427 <tt>text = {{url.text}}</tt><br/>
21428 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21429 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21430 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21431 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21432 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21433 </form>
21434 </file>
21435 <file name="protractor.js" type="protractor">
21436 var text = element(by.binding('url.text'));
21437 var valid = element(by.binding('myForm.input.$valid'));
21438 var input = element(by.model('url.text'));
21439
21440 it('should initialize to model', function() {
21441 expect(text.getText()).toContain('http://google.com');
21442 expect(valid.getText()).toContain('true');
21443 });
21444
21445 it('should be invalid if empty', function() {
21446 input.clear();
21447 input.sendKeys('');
21448
21449 expect(text.getText()).toEqual('text =');
21450 expect(valid.getText()).toContain('false');
21451 });
21452
21453 it('should be invalid if not url', function() {
21454 input.clear();
21455 input.sendKeys('box');
21456
21457 expect(valid.getText()).toContain('false');
21458 });
21459 </file>
21460 </example>
21461 */
21462 'url': urlInputType,
21463
21464
21465 /**
21466 * @ngdoc input
21467 * @name input[email]
21468 *
21469 * @description
21470 * Text input with email validation. Sets the `email` validation error key if not a valid email
21471 * address.
21472 *
21473 * <div class="alert alert-warning">
21474 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21475 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21476 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21477 * </div>
21478 *
21479 * @param {string} ngModel Assignable angular expression to data-bind to.
21480 * @param {string=} name Property name of the form under which the control is published.
21481 * @param {string=} required Sets `required` validation error key if the value is not entered.
21482 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21483 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21484 * `required` when you want to data-bind to the `required` attribute.
21485 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21486 * minlength.
21487 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21488 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21489 * any length.
21490 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21491 * that contains the regular expression body that will be converted to a regular expression
21492 * as in the ngPattern directive.
21493 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21494 * a RegExp found by evaluating the Angular expression given in the attribute value.
21495 * If the expression evaluates to a RegExp object, then this is used directly.
21496 * If the expression evaluates to a string, then it will be converted to a RegExp
21497 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21498 * `new RegExp('^abc$')`.<br />
21499 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21500 * start at the index of the last search's match, thus not taking the whole input value into
21501 * account.
21502 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21503 * interaction with the input element.
21504 *
21505 * @example
21506 <example name="email-input-directive" module="emailExample">
21507 <file name="index.html">
21508 <script>
21509 angular.module('emailExample', [])
21510 .controller('ExampleController', ['$scope', function($scope) {
21511 $scope.email = {
21512 text: 'me@example.com'
21513 };
21514 }]);
21515 </script>
21516 <form name="myForm" ng-controller="ExampleController">
21517 <label>Email:
21518 <input type="email" name="input" ng-model="email.text" required>
21519 </label>
21520 <div role="alert">
21521 <span class="error" ng-show="myForm.input.$error.required">
21522 Required!</span>
21523 <span class="error" ng-show="myForm.input.$error.email">
21524 Not valid email!</span>
21525 </div>
21526 <tt>text = {{email.text}}</tt><br/>
21527 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21528 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21529 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21530 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21531 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21532 </form>
21533 </file>
21534 <file name="protractor.js" type="protractor">
21535 var text = element(by.binding('email.text'));
21536 var valid = element(by.binding('myForm.input.$valid'));
21537 var input = element(by.model('email.text'));
21538
21539 it('should initialize to model', function() {
21540 expect(text.getText()).toContain('me@example.com');
21541 expect(valid.getText()).toContain('true');
21542 });
21543
21544 it('should be invalid if empty', function() {
21545 input.clear();
21546 input.sendKeys('');
21547 expect(text.getText()).toEqual('text =');
21548 expect(valid.getText()).toContain('false');
21549 });
21550
21551 it('should be invalid if not email', function() {
21552 input.clear();
21553 input.sendKeys('xxx');
21554
21555 expect(valid.getText()).toContain('false');
21556 });
21557 </file>
21558 </example>
21559 */
21560 'email': emailInputType,
21561
21562
21563 /**
21564 * @ngdoc input
21565 * @name input[radio]
21566 *
21567 * @description
21568 * HTML radio button.
21569 *
21570 * @param {string} ngModel Assignable angular expression to data-bind to.
21571 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21572 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21573 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21574 * @param {string=} name Property name of the form under which the control is published.
21575 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21576 * interaction with the input element.
21577 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21578 * is selected. Should be used instead of the `value` attribute if you need
21579 * a non-string `ngModel` (`boolean`, `array`, ...).
21580 *
21581 * @example
21582 <example name="radio-input-directive" module="radioExample">
21583 <file name="index.html">
21584 <script>
21585 angular.module('radioExample', [])
21586 .controller('ExampleController', ['$scope', function($scope) {
21587 $scope.color = {
21588 name: 'blue'
21589 };
21590 $scope.specialValue = {
21591 "id": "12345",
21592 "value": "green"
21593 };
21594 }]);
21595 </script>
21596 <form name="myForm" ng-controller="ExampleController">
21597 <label>
21598 <input type="radio" ng-model="color.name" value="red">
21599 Red
21600 </label><br/>
21601 <label>
21602 <input type="radio" ng-model="color.name" ng-value="specialValue">
21603 Green
21604 </label><br/>
21605 <label>
21606 <input type="radio" ng-model="color.name" value="blue">
21607 Blue
21608 </label><br/>
21609 <tt>color = {{color.name | json}}</tt><br/>
21610 </form>
21611 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21612 </file>
21613 <file name="protractor.js" type="protractor">
21614 it('should change state', function() {
21615 var color = element(by.binding('color.name'));
21616
21617 expect(color.getText()).toContain('blue');
21618
21619 element.all(by.model('color.name')).get(0).click();
21620
21621 expect(color.getText()).toContain('red');
21622 });
21623 </file>
21624 </example>
21625 */
21626 'radio': radioInputType,
21627
21628
21629 /**
21630 * @ngdoc input
21631 * @name input[checkbox]
21632 *
21633 * @description
21634 * HTML checkbox.
21635 *
21636 * @param {string} ngModel Assignable angular expression to data-bind to.
21637 * @param {string=} name Property name of the form under which the control is published.
21638 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21639 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21640 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21641 * interaction with the input element.
21642 *
21643 * @example
21644 <example name="checkbox-input-directive" module="checkboxExample">
21645 <file name="index.html">
21646 <script>
21647 angular.module('checkboxExample', [])
21648 .controller('ExampleController', ['$scope', function($scope) {
21649 $scope.checkboxModel = {
21650 value1 : true,
21651 value2 : 'YES'
21652 };
21653 }]);
21654 </script>
21655 <form name="myForm" ng-controller="ExampleController">
21656 <label>Value1:
21657 <input type="checkbox" ng-model="checkboxModel.value1">
21658 </label><br/>
21659 <label>Value2:
21660 <input type="checkbox" ng-model="checkboxModel.value2"
21661 ng-true-value="'YES'" ng-false-value="'NO'">
21662 </label><br/>
21663 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21664 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21665 </form>
21666 </file>
21667 <file name="protractor.js" type="protractor">
21668 it('should change state', function() {
21669 var value1 = element(by.binding('checkboxModel.value1'));
21670 var value2 = element(by.binding('checkboxModel.value2'));
21671
21672 expect(value1.getText()).toContain('true');
21673 expect(value2.getText()).toContain('YES');
21674
21675 element(by.model('checkboxModel.value1')).click();
21676 element(by.model('checkboxModel.value2')).click();
21677
21678 expect(value1.getText()).toContain('false');
21679 expect(value2.getText()).toContain('NO');
21680 });
21681 </file>
21682 </example>
21683 */
21684 'checkbox': checkboxInputType,
21685
21686 'hidden': noop,
21687 'button': noop,
21688 'submit': noop,
21689 'reset': noop,
21690 'file': noop
21691};
21692
21693function stringBasedInputType(ctrl) {
21694 ctrl.$formatters.push(function(value) {
21695 return ctrl.$isEmpty(value) ? value : value.toString();
21696 });
21697}
21698
21699function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21700 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21701 stringBasedInputType(ctrl);
21702}
21703
21704function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21705 var type = lowercase(element[0].type);
21706
21707 // In composition mode, users are still inputing intermediate text buffer,
21708 // hold the listener until composition is done.
21709 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21710 if (!$sniffer.android) {
21711 var composing = false;
21712
21713 element.on('compositionstart', function(data) {
21714 composing = true;
21715 });
21716
21717 element.on('compositionend', function() {
21718 composing = false;
21719 listener();
21720 });
21721 }
21722
21723 var listener = function(ev) {
21724 if (timeout) {
21725 $browser.defer.cancel(timeout);
21726 timeout = null;
21727 }
21728 if (composing) return;
21729 var value = element.val(),
21730 event = ev && ev.type;
21731
21732 // By default we will trim the value
21733 // If the attribute ng-trim exists we will avoid trimming
21734 // If input type is 'password', the value is never trimmed
21735 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21736 value = trim(value);
21737 }
21738
21739 // If a control is suffering from bad input (due to native validators), browsers discard its
21740 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21741 // control's value is the same empty value twice in a row.
21742 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21743 ctrl.$setViewValue(value, event);
21744 }
21745 };
21746
21747 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21748 // input event on backspace, delete or cut
21749 if ($sniffer.hasEvent('input')) {
21750 element.on('input', listener);
21751 } else {
21752 var timeout;
21753
21754 var deferListener = function(ev, input, origValue) {
21755 if (!timeout) {
21756 timeout = $browser.defer(function() {
21757 timeout = null;
21758 if (!input || input.value !== origValue) {
21759 listener(ev);
21760 }
21761 });
21762 }
21763 };
21764
21765 element.on('keydown', function(event) {
21766 var key = event.keyCode;
21767
21768 // ignore
21769 // command modifiers arrows
21770 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21771
21772 deferListener(event, this, this.value);
21773 });
21774
21775 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21776 if ($sniffer.hasEvent('paste')) {
21777 element.on('paste cut', deferListener);
21778 }
21779 }
21780
21781 // if user paste into input using mouse on older browser
21782 // or form autocomplete on newer browser, we need "change" event to catch it
21783 element.on('change', listener);
21784
21785 ctrl.$render = function() {
21786 // Workaround for Firefox validation #12102.
21787 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21788 if (element.val() !== value) {
21789 element.val(value);
21790 }
21791 };
21792}
21793
21794function weekParser(isoWeek, existingDate) {
21795 if (isDate(isoWeek)) {
21796 return isoWeek;
21797 }
21798
21799 if (isString(isoWeek)) {
21800 WEEK_REGEXP.lastIndex = 0;
21801 var parts = WEEK_REGEXP.exec(isoWeek);
21802 if (parts) {
21803 var year = +parts[1],
21804 week = +parts[2],
21805 hours = 0,
21806 minutes = 0,
21807 seconds = 0,
21808 milliseconds = 0,
21809 firstThurs = getFirstThursdayOfYear(year),
21810 addDays = (week - 1) * 7;
21811
21812 if (existingDate) {
21813 hours = existingDate.getHours();
21814 minutes = existingDate.getMinutes();
21815 seconds = existingDate.getSeconds();
21816 milliseconds = existingDate.getMilliseconds();
21817 }
21818
21819 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21820 }
21821 }
21822
21823 return NaN;
21824}
21825
21826function createDateParser(regexp, mapping) {
21827 return function(iso, date) {
21828 var parts, map;
21829
21830 if (isDate(iso)) {
21831 return iso;
21832 }
21833
21834 if (isString(iso)) {
21835 // When a date is JSON'ified to wraps itself inside of an extra
21836 // set of double quotes. This makes the date parsing code unable
21837 // to match the date string and parse it as a date.
21838 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21839 iso = iso.substring(1, iso.length - 1);
21840 }
21841 if (ISO_DATE_REGEXP.test(iso)) {
21842 return new Date(iso);
21843 }
21844 regexp.lastIndex = 0;
21845 parts = regexp.exec(iso);
21846
21847 if (parts) {
21848 parts.shift();
21849 if (date) {
21850 map = {
21851 yyyy: date.getFullYear(),
21852 MM: date.getMonth() + 1,
21853 dd: date.getDate(),
21854 HH: date.getHours(),
21855 mm: date.getMinutes(),
21856 ss: date.getSeconds(),
21857 sss: date.getMilliseconds() / 1000
21858 };
21859 } else {
21860 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21861 }
21862
21863 forEach(parts, function(part, index) {
21864 if (index < mapping.length) {
21865 map[mapping[index]] = +part;
21866 }
21867 });
21868 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21869 }
21870 }
21871
21872 return NaN;
21873 };
21874}
21875
21876function createDateInputType(type, regexp, parseDate, format) {
21877 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21878 badInputChecker(scope, element, attr, ctrl);
21879 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21880 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21881 var previousDate;
21882
21883 ctrl.$$parserName = type;
21884 ctrl.$parsers.push(function(value) {
21885 if (ctrl.$isEmpty(value)) return null;
21886 if (regexp.test(value)) {
21887 // Note: We cannot read ctrl.$modelValue, as there might be a different
21888 // parser/formatter in the processing chain so that the model
21889 // contains some different data format!
21890 var parsedDate = parseDate(value, previousDate);
21891 if (timezone) {
21892 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21893 }
21894 return parsedDate;
21895 }
21896 return undefined;
21897 });
21898
21899 ctrl.$formatters.push(function(value) {
21900 if (value && !isDate(value)) {
21901 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21902 }
21903 if (isValidDate(value)) {
21904 previousDate = value;
21905 if (previousDate && timezone) {
21906 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21907 }
21908 return $filter('date')(value, format, timezone);
21909 } else {
21910 previousDate = null;
21911 return '';
21912 }
21913 });
21914
21915 if (isDefined(attr.min) || attr.ngMin) {
21916 var minVal;
21917 ctrl.$validators.min = function(value) {
21918 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21919 };
21920 attr.$observe('min', function(val) {
21921 minVal = parseObservedDateValue(val);
21922 ctrl.$validate();
21923 });
21924 }
21925
21926 if (isDefined(attr.max) || attr.ngMax) {
21927 var maxVal;
21928 ctrl.$validators.max = function(value) {
21929 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
21930 };
21931 attr.$observe('max', function(val) {
21932 maxVal = parseObservedDateValue(val);
21933 ctrl.$validate();
21934 });
21935 }
21936
21937 function isValidDate(value) {
21938 // Invalid Date: getTime() returns NaN
21939 return value && !(value.getTime && value.getTime() !== value.getTime());
21940 }
21941
21942 function parseObservedDateValue(val) {
21943 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
21944 }
21945 };
21946}
21947
21948function badInputChecker(scope, element, attr, ctrl) {
21949 var node = element[0];
21950 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
21951 if (nativeValidation) {
21952 ctrl.$parsers.push(function(value) {
21953 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
21954 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
21955 // - also sets validity.badInput (should only be validity.typeMismatch).
21956 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
21957 // - can ignore this case as we can still read out the erroneous email...
21958 return validity.badInput && !validity.typeMismatch ? undefined : value;
21959 });
21960 }
21961}
21962
21963function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21964 badInputChecker(scope, element, attr, ctrl);
21965 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21966
21967 ctrl.$$parserName = 'number';
21968 ctrl.$parsers.push(function(value) {
21969 if (ctrl.$isEmpty(value)) return null;
21970 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
21971 return undefined;
21972 });
21973
21974 ctrl.$formatters.push(function(value) {
21975 if (!ctrl.$isEmpty(value)) {
21976 if (!isNumber(value)) {
21977 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
21978 }
21979 value = value.toString();
21980 }
21981 return value;
21982 });
21983
21984 if (isDefined(attr.min) || attr.ngMin) {
21985 var minVal;
21986 ctrl.$validators.min = function(value) {
21987 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
21988 };
21989
21990 attr.$observe('min', function(val) {
21991 if (isDefined(val) && !isNumber(val)) {
21992 val = parseFloat(val, 10);
21993 }
21994 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
21995 // TODO(matsko): implement validateLater to reduce number of validations
21996 ctrl.$validate();
21997 });
21998 }
21999
22000 if (isDefined(attr.max) || attr.ngMax) {
22001 var maxVal;
22002 ctrl.$validators.max = function(value) {
22003 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22004 };
22005
22006 attr.$observe('max', function(val) {
22007 if (isDefined(val) && !isNumber(val)) {
22008 val = parseFloat(val, 10);
22009 }
22010 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22011 // TODO(matsko): implement validateLater to reduce number of validations
22012 ctrl.$validate();
22013 });
22014 }
22015}
22016
22017function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22018 // Note: no badInputChecker here by purpose as `url` is only a validation
22019 // in browsers, i.e. we can always read out input.value even if it is not valid!
22020 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22021 stringBasedInputType(ctrl);
22022
22023 ctrl.$$parserName = 'url';
22024 ctrl.$validators.url = function(modelValue, viewValue) {
22025 var value = modelValue || viewValue;
22026 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22027 };
22028}
22029
22030function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22031 // Note: no badInputChecker here by purpose as `url` is only a validation
22032 // in browsers, i.e. we can always read out input.value even if it is not valid!
22033 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22034 stringBasedInputType(ctrl);
22035
22036 ctrl.$$parserName = 'email';
22037 ctrl.$validators.email = function(modelValue, viewValue) {
22038 var value = modelValue || viewValue;
22039 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22040 };
22041}
22042
22043function radioInputType(scope, element, attr, ctrl) {
22044 // make the name unique, if not defined
22045 if (isUndefined(attr.name)) {
22046 element.attr('name', nextUid());
22047 }
22048
22049 var listener = function(ev) {
22050 if (element[0].checked) {
22051 ctrl.$setViewValue(attr.value, ev && ev.type);
22052 }
22053 };
22054
22055 element.on('click', listener);
22056
22057 ctrl.$render = function() {
22058 var value = attr.value;
22059 element[0].checked = (value == ctrl.$viewValue);
22060 };
22061
22062 attr.$observe('value', ctrl.$render);
22063}
22064
22065function parseConstantExpr($parse, context, name, expression, fallback) {
22066 var parseFn;
22067 if (isDefined(expression)) {
22068 parseFn = $parse(expression);
22069 if (!parseFn.constant) {
22070 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22071 '`{1}`.', name, expression);
22072 }
22073 return parseFn(context);
22074 }
22075 return fallback;
22076}
22077
22078function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22079 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22080 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22081
22082 var listener = function(ev) {
22083 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22084 };
22085
22086 element.on('click', listener);
22087
22088 ctrl.$render = function() {
22089 element[0].checked = ctrl.$viewValue;
22090 };
22091
22092 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22093 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22094 // it to a boolean.
22095 ctrl.$isEmpty = function(value) {
22096 return value === false;
22097 };
22098
22099 ctrl.$formatters.push(function(value) {
22100 return equals(value, trueValue);
22101 });
22102
22103 ctrl.$parsers.push(function(value) {
22104 return value ? trueValue : falseValue;
22105 });
22106}
22107
22108
22109/**
22110 * @ngdoc directive
22111 * @name textarea
22112 * @restrict E
22113 *
22114 * @description
22115 * HTML textarea element control with angular data-binding. The data-binding and validation
22116 * properties of this element are exactly the same as those of the
22117 * {@link ng.directive:input input element}.
22118 *
22119 * @param {string} ngModel Assignable angular expression to data-bind to.
22120 * @param {string=} name Property name of the form under which the control is published.
22121 * @param {string=} required Sets `required` validation error key if the value is not entered.
22122 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22123 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22124 * `required` when you want to data-bind to the `required` attribute.
22125 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22126 * minlength.
22127 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22128 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22129 * length.
22130 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22131 * a RegExp found by evaluating the Angular expression given in the attribute value.
22132 * If the expression evaluates to a RegExp object, then this is used directly.
22133 * If the expression evaluates to a string, then it will be converted to a RegExp
22134 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22135 * `new RegExp('^abc$')`.<br />
22136 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22137 * start at the index of the last search's match, thus not taking the whole input value into
22138 * account.
22139 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22140 * interaction with the input element.
22141 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22142 */
22143
22144
22145/**
22146 * @ngdoc directive
22147 * @name input
22148 * @restrict E
22149 *
22150 * @description
22151 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22152 * input state control, and validation.
22153 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22154 *
22155 * <div class="alert alert-warning">
22156 * **Note:** Not every feature offered is available for all input types.
22157 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22158 * </div>
22159 *
22160 * @param {string} ngModel Assignable angular expression to data-bind to.
22161 * @param {string=} name Property name of the form under which the control is published.
22162 * @param {string=} required Sets `required` validation error key if the value is not entered.
22163 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22164 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22165 * minlength.
22166 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22167 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22168 * length.
22169 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22170 * a RegExp found by evaluating the Angular expression given in the attribute value.
22171 * If the expression evaluates to a RegExp object, then this is used directly.
22172 * If the expression evaluates to a string, then it will be converted to a RegExp
22173 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22174 * `new RegExp('^abc$')`.<br />
22175 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22176 * start at the index of the last search's match, thus not taking the whole input value into
22177 * account.
22178 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22179 * interaction with the input element.
22180 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22181 * This parameter is ignored for input[type=password] controls, which will never trim the
22182 * input.
22183 *
22184 * @example
22185 <example name="input-directive" module="inputExample">
22186 <file name="index.html">
22187 <script>
22188 angular.module('inputExample', [])
22189 .controller('ExampleController', ['$scope', function($scope) {
22190 $scope.user = {name: 'guest', last: 'visitor'};
22191 }]);
22192 </script>
22193 <div ng-controller="ExampleController">
22194 <form name="myForm">
22195 <label>
22196 User name:
22197 <input type="text" name="userName" ng-model="user.name" required>
22198 </label>
22199 <div role="alert">
22200 <span class="error" ng-show="myForm.userName.$error.required">
22201 Required!</span>
22202 </div>
22203 <label>
22204 Last name:
22205 <input type="text" name="lastName" ng-model="user.last"
22206 ng-minlength="3" ng-maxlength="10">
22207 </label>
22208 <div role="alert">
22209 <span class="error" ng-show="myForm.lastName.$error.minlength">
22210 Too short!</span>
22211 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22212 Too long!</span>
22213 </div>
22214 </form>
22215 <hr>
22216 <tt>user = {{user}}</tt><br/>
22217 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22218 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22219 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22220 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22221 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22222 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22223 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22224 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22225 </div>
22226 </file>
22227 <file name="protractor.js" type="protractor">
22228 var user = element(by.exactBinding('user'));
22229 var userNameValid = element(by.binding('myForm.userName.$valid'));
22230 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22231 var lastNameError = element(by.binding('myForm.lastName.$error'));
22232 var formValid = element(by.binding('myForm.$valid'));
22233 var userNameInput = element(by.model('user.name'));
22234 var userLastInput = element(by.model('user.last'));
22235
22236 it('should initialize to model', function() {
22237 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22238 expect(userNameValid.getText()).toContain('true');
22239 expect(formValid.getText()).toContain('true');
22240 });
22241
22242 it('should be invalid if empty when required', function() {
22243 userNameInput.clear();
22244 userNameInput.sendKeys('');
22245
22246 expect(user.getText()).toContain('{"last":"visitor"}');
22247 expect(userNameValid.getText()).toContain('false');
22248 expect(formValid.getText()).toContain('false');
22249 });
22250
22251 it('should be valid if empty when min length is set', function() {
22252 userLastInput.clear();
22253 userLastInput.sendKeys('');
22254
22255 expect(user.getText()).toContain('{"name":"guest","last":""}');
22256 expect(lastNameValid.getText()).toContain('true');
22257 expect(formValid.getText()).toContain('true');
22258 });
22259
22260 it('should be invalid if less than required min length', function() {
22261 userLastInput.clear();
22262 userLastInput.sendKeys('xx');
22263
22264 expect(user.getText()).toContain('{"name":"guest"}');
22265 expect(lastNameValid.getText()).toContain('false');
22266 expect(lastNameError.getText()).toContain('minlength');
22267 expect(formValid.getText()).toContain('false');
22268 });
22269
22270 it('should be invalid if longer than max length', function() {
22271 userLastInput.clear();
22272 userLastInput.sendKeys('some ridiculously long name');
22273
22274 expect(user.getText()).toContain('{"name":"guest"}');
22275 expect(lastNameValid.getText()).toContain('false');
22276 expect(lastNameError.getText()).toContain('maxlength');
22277 expect(formValid.getText()).toContain('false');
22278 });
22279 </file>
22280 </example>
22281 */
22282var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22283 function($browser, $sniffer, $filter, $parse) {
22284 return {
22285 restrict: 'E',
22286 require: ['?ngModel'],
22287 link: {
22288 pre: function(scope, element, attr, ctrls) {
22289 if (ctrls[0]) {
22290 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22291 $browser, $filter, $parse);
22292 }
22293 }
22294 }
22295 };
22296}];
22297
22298
22299
22300var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22301/**
22302 * @ngdoc directive
22303 * @name ngValue
22304 *
22305 * @description
22306 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22307 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22308 * the bound value.
22309 *
22310 * `ngValue` is useful when dynamically generating lists of radio buttons using
22311 * {@link ngRepeat `ngRepeat`}, as shown below.
22312 *
22313 * Likewise, `ngValue` can be used to generate `<option>` elements for
22314 * the {@link select `select`} element. In that case however, only strings are supported
22315 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22316 * Support for `select` models with non-string values is available via `ngOptions`.
22317 *
22318 * @element input
22319 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22320 * of the `input` element
22321 *
22322 * @example
22323 <example name="ngValue-directive" module="valueExample">
22324 <file name="index.html">
22325 <script>
22326 angular.module('valueExample', [])
22327 .controller('ExampleController', ['$scope', function($scope) {
22328 $scope.names = ['pizza', 'unicorns', 'robots'];
22329 $scope.my = { favorite: 'unicorns' };
22330 }]);
22331 </script>
22332 <form ng-controller="ExampleController">
22333 <h2>Which is your favorite?</h2>
22334 <label ng-repeat="name in names" for="{{name}}">
22335 {{name}}
22336 <input type="radio"
22337 ng-model="my.favorite"
22338 ng-value="name"
22339 id="{{name}}"
22340 name="favorite">
22341 </label>
22342 <div>You chose {{my.favorite}}</div>
22343 </form>
22344 </file>
22345 <file name="protractor.js" type="protractor">
22346 var favorite = element(by.binding('my.favorite'));
22347
22348 it('should initialize to model', function() {
22349 expect(favorite.getText()).toContain('unicorns');
22350 });
22351 it('should bind the values to the inputs', function() {
22352 element.all(by.model('my.favorite')).get(0).click();
22353 expect(favorite.getText()).toContain('pizza');
22354 });
22355 </file>
22356 </example>
22357 */
22358var ngValueDirective = function() {
22359 return {
22360 restrict: 'A',
22361 priority: 100,
22362 compile: function(tpl, tplAttr) {
22363 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22364 return function ngValueConstantLink(scope, elm, attr) {
22365 attr.$set('value', scope.$eval(attr.ngValue));
22366 };
22367 } else {
22368 return function ngValueLink(scope, elm, attr) {
22369 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22370 attr.$set('value', value);
22371 });
22372 };
22373 }
22374 }
22375 };
22376};
22377
22378/**
22379 * @ngdoc directive
22380 * @name ngBind
22381 * @restrict AC
22382 *
22383 * @description
22384 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22385 * with the value of a given expression, and to update the text content when the value of that
22386 * expression changes.
22387 *
22388 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22389 * `{{ expression }}` which is similar but less verbose.
22390 *
22391 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22392 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22393 * element attribute, it makes the bindings invisible to the user while the page is loading.
22394 *
22395 * An alternative solution to this problem would be using the
22396 * {@link ng.directive:ngCloak ngCloak} directive.
22397 *
22398 *
22399 * @element ANY
22400 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22401 *
22402 * @example
22403 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22404 <example module="bindExample">
22405 <file name="index.html">
22406 <script>
22407 angular.module('bindExample', [])
22408 .controller('ExampleController', ['$scope', function($scope) {
22409 $scope.name = 'Whirled';
22410 }]);
22411 </script>
22412 <div ng-controller="ExampleController">
22413 <label>Enter name: <input type="text" ng-model="name"></label><br>
22414 Hello <span ng-bind="name"></span>!
22415 </div>
22416 </file>
22417 <file name="protractor.js" type="protractor">
22418 it('should check ng-bind', function() {
22419 var nameInput = element(by.model('name'));
22420
22421 expect(element(by.binding('name')).getText()).toBe('Whirled');
22422 nameInput.clear();
22423 nameInput.sendKeys('world');
22424 expect(element(by.binding('name')).getText()).toBe('world');
22425 });
22426 </file>
22427 </example>
22428 */
22429var ngBindDirective = ['$compile', function($compile) {
22430 return {
22431 restrict: 'AC',
22432 compile: function ngBindCompile(templateElement) {
22433 $compile.$$addBindingClass(templateElement);
22434 return function ngBindLink(scope, element, attr) {
22435 $compile.$$addBindingInfo(element, attr.ngBind);
22436 element = element[0];
22437 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22438 element.textContent = isUndefined(value) ? '' : value;
22439 });
22440 };
22441 }
22442 };
22443}];
22444
22445
22446/**
22447 * @ngdoc directive
22448 * @name ngBindTemplate
22449 *
22450 * @description
22451 * The `ngBindTemplate` directive specifies that the element
22452 * text content should be replaced with the interpolation of the template
22453 * in the `ngBindTemplate` attribute.
22454 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22455 * expressions. This directive is needed since some HTML elements
22456 * (such as TITLE and OPTION) cannot contain SPAN elements.
22457 *
22458 * @element ANY
22459 * @param {string} ngBindTemplate template of form
22460 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22461 *
22462 * @example
22463 * Try it here: enter text in text box and watch the greeting change.
22464 <example module="bindExample">
22465 <file name="index.html">
22466 <script>
22467 angular.module('bindExample', [])
22468 .controller('ExampleController', ['$scope', function($scope) {
22469 $scope.salutation = 'Hello';
22470 $scope.name = 'World';
22471 }]);
22472 </script>
22473 <div ng-controller="ExampleController">
22474 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22475 <label>Name: <input type="text" ng-model="name"></label><br>
22476 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22477 </div>
22478 </file>
22479 <file name="protractor.js" type="protractor">
22480 it('should check ng-bind', function() {
22481 var salutationElem = element(by.binding('salutation'));
22482 var salutationInput = element(by.model('salutation'));
22483 var nameInput = element(by.model('name'));
22484
22485 expect(salutationElem.getText()).toBe('Hello World!');
22486
22487 salutationInput.clear();
22488 salutationInput.sendKeys('Greetings');
22489 nameInput.clear();
22490 nameInput.sendKeys('user');
22491
22492 expect(salutationElem.getText()).toBe('Greetings user!');
22493 });
22494 </file>
22495 </example>
22496 */
22497var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22498 return {
22499 compile: function ngBindTemplateCompile(templateElement) {
22500 $compile.$$addBindingClass(templateElement);
22501 return function ngBindTemplateLink(scope, element, attr) {
22502 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22503 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22504 element = element[0];
22505 attr.$observe('ngBindTemplate', function(value) {
22506 element.textContent = isUndefined(value) ? '' : value;
22507 });
22508 };
22509 }
22510 };
22511}];
22512
22513
22514/**
22515 * @ngdoc directive
22516 * @name ngBindHtml
22517 *
22518 * @description
22519 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22520 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22521 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22522 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22523 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22524 *
22525 * You may also bypass sanitization for values you know are safe. To do so, bind to
22526 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22527 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22528 *
22529 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22530 * will have an exception (instead of an exploit.)
22531 *
22532 * @element ANY
22533 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22534 *
22535 * @example
22536
22537 <example module="bindHtmlExample" deps="angular-sanitize.js">
22538 <file name="index.html">
22539 <div ng-controller="ExampleController">
22540 <p ng-bind-html="myHTML"></p>
22541 </div>
22542 </file>
22543
22544 <file name="script.js">
22545 angular.module('bindHtmlExample', ['ngSanitize'])
22546 .controller('ExampleController', ['$scope', function($scope) {
22547 $scope.myHTML =
22548 'I am an <code>HTML</code>string with ' +
22549 '<a href="#">links!</a> and other <em>stuff</em>';
22550 }]);
22551 </file>
22552
22553 <file name="protractor.js" type="protractor">
22554 it('should check ng-bind-html', function() {
22555 expect(element(by.binding('myHTML')).getText()).toBe(
22556 'I am an HTMLstring with links! and other stuff');
22557 });
22558 </file>
22559 </example>
22560 */
22561var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22562 return {
22563 restrict: 'A',
22564 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22565 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22566 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22567 return (value || '').toString();
22568 });
22569 $compile.$$addBindingClass(tElement);
22570
22571 return function ngBindHtmlLink(scope, element, attr) {
22572 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22573
22574 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22575 // we re-evaluate the expr because we want a TrustedValueHolderType
22576 // for $sce, not a string
22577 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22578 });
22579 };
22580 }
22581 };
22582}];
22583
22584/**
22585 * @ngdoc directive
22586 * @name ngChange
22587 *
22588 * @description
22589 * Evaluate the given expression when the user changes the input.
22590 * The expression is evaluated immediately, unlike the JavaScript onchange event
22591 * which only triggers at the end of a change (usually, when the user leaves the
22592 * form element or presses the return key).
22593 *
22594 * The `ngChange` expression is only evaluated when a change in the input value causes
22595 * a new value to be committed to the model.
22596 *
22597 * It will not be evaluated:
22598 * * if the value returned from the `$parsers` transformation pipeline has not changed
22599 * * if the input has continued to be invalid since the model will stay `null`
22600 * * if the model is changed programmatically and not by a change to the input value
22601 *
22602 *
22603 * Note, this directive requires `ngModel` to be present.
22604 *
22605 * @element input
22606 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22607 * in input value.
22608 *
22609 * @example
22610 * <example name="ngChange-directive" module="changeExample">
22611 * <file name="index.html">
22612 * <script>
22613 * angular.module('changeExample', [])
22614 * .controller('ExampleController', ['$scope', function($scope) {
22615 * $scope.counter = 0;
22616 * $scope.change = function() {
22617 * $scope.counter++;
22618 * };
22619 * }]);
22620 * </script>
22621 * <div ng-controller="ExampleController">
22622 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22623 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22624 * <label for="ng-change-example2">Confirmed</label><br />
22625 * <tt>debug = {{confirmed}}</tt><br/>
22626 * <tt>counter = {{counter}}</tt><br/>
22627 * </div>
22628 * </file>
22629 * <file name="protractor.js" type="protractor">
22630 * var counter = element(by.binding('counter'));
22631 * var debug = element(by.binding('confirmed'));
22632 *
22633 * it('should evaluate the expression if changing from view', function() {
22634 * expect(counter.getText()).toContain('0');
22635 *
22636 * element(by.id('ng-change-example1')).click();
22637 *
22638 * expect(counter.getText()).toContain('1');
22639 * expect(debug.getText()).toContain('true');
22640 * });
22641 *
22642 * it('should not evaluate the expression if changing from model', function() {
22643 * element(by.id('ng-change-example2')).click();
22644
22645 * expect(counter.getText()).toContain('0');
22646 * expect(debug.getText()).toContain('true');
22647 * });
22648 * </file>
22649 * </example>
22650 */
22651var ngChangeDirective = valueFn({
22652 restrict: 'A',
22653 require: 'ngModel',
22654 link: function(scope, element, attr, ctrl) {
22655 ctrl.$viewChangeListeners.push(function() {
22656 scope.$eval(attr.ngChange);
22657 });
22658 }
22659});
22660
22661function classDirective(name, selector) {
22662 name = 'ngClass' + name;
22663 return ['$animate', function($animate) {
22664 return {
22665 restrict: 'AC',
22666 link: function(scope, element, attr) {
22667 var oldVal;
22668
22669 scope.$watch(attr[name], ngClassWatchAction, true);
22670
22671 attr.$observe('class', function(value) {
22672 ngClassWatchAction(scope.$eval(attr[name]));
22673 });
22674
22675
22676 if (name !== 'ngClass') {
22677 scope.$watch('$index', function($index, old$index) {
22678 // jshint bitwise: false
22679 var mod = $index & 1;
22680 if (mod !== (old$index & 1)) {
22681 var classes = arrayClasses(scope.$eval(attr[name]));
22682 mod === selector ?
22683 addClasses(classes) :
22684 removeClasses(classes);
22685 }
22686 });
22687 }
22688
22689 function addClasses(classes) {
22690 var newClasses = digestClassCounts(classes, 1);
22691 attr.$addClass(newClasses);
22692 }
22693
22694 function removeClasses(classes) {
22695 var newClasses = digestClassCounts(classes, -1);
22696 attr.$removeClass(newClasses);
22697 }
22698
22699 function digestClassCounts(classes, count) {
22700 // Use createMap() to prevent class assumptions involving property
22701 // names in Object.prototype
22702 var classCounts = element.data('$classCounts') || createMap();
22703 var classesToUpdate = [];
22704 forEach(classes, function(className) {
22705 if (count > 0 || classCounts[className]) {
22706 classCounts[className] = (classCounts[className] || 0) + count;
22707 if (classCounts[className] === +(count > 0)) {
22708 classesToUpdate.push(className);
22709 }
22710 }
22711 });
22712 element.data('$classCounts', classCounts);
22713 return classesToUpdate.join(' ');
22714 }
22715
22716 function updateClasses(oldClasses, newClasses) {
22717 var toAdd = arrayDifference(newClasses, oldClasses);
22718 var toRemove = arrayDifference(oldClasses, newClasses);
22719 toAdd = digestClassCounts(toAdd, 1);
22720 toRemove = digestClassCounts(toRemove, -1);
22721 if (toAdd && toAdd.length) {
22722 $animate.addClass(element, toAdd);
22723 }
22724 if (toRemove && toRemove.length) {
22725 $animate.removeClass(element, toRemove);
22726 }
22727 }
22728
22729 function ngClassWatchAction(newVal) {
22730 if (selector === true || scope.$index % 2 === selector) {
22731 var newClasses = arrayClasses(newVal || []);
22732 if (!oldVal) {
22733 addClasses(newClasses);
22734 } else if (!equals(newVal,oldVal)) {
22735 var oldClasses = arrayClasses(oldVal);
22736 updateClasses(oldClasses, newClasses);
22737 }
22738 }
22739 oldVal = shallowCopy(newVal);
22740 }
22741 }
22742 };
22743
22744 function arrayDifference(tokens1, tokens2) {
22745 var values = [];
22746
22747 outer:
22748 for (var i = 0; i < tokens1.length; i++) {
22749 var token = tokens1[i];
22750 for (var j = 0; j < tokens2.length; j++) {
22751 if (token == tokens2[j]) continue outer;
22752 }
22753 values.push(token);
22754 }
22755 return values;
22756 }
22757
22758 function arrayClasses(classVal) {
22759 var classes = [];
22760 if (isArray(classVal)) {
22761 forEach(classVal, function(v) {
22762 classes = classes.concat(arrayClasses(v));
22763 });
22764 return classes;
22765 } else if (isString(classVal)) {
22766 return classVal.split(' ');
22767 } else if (isObject(classVal)) {
22768 forEach(classVal, function(v, k) {
22769 if (v) {
22770 classes = classes.concat(k.split(' '));
22771 }
22772 });
22773 return classes;
22774 }
22775 return classVal;
22776 }
22777 }];
22778}
22779
22780/**
22781 * @ngdoc directive
22782 * @name ngClass
22783 * @restrict AC
22784 *
22785 * @description
22786 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22787 * an expression that represents all classes to be added.
22788 *
22789 * The directive operates in three different ways, depending on which of three types the expression
22790 * evaluates to:
22791 *
22792 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22793 * names.
22794 *
22795 * 2. If the expression evaluates to an object, then for each key-value pair of the
22796 * object with a truthy value the corresponding key is used as a class name.
22797 *
22798 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22799 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22800 * to give you more control over what CSS classes appear. See the code below for an example of this.
22801 *
22802 *
22803 * The directive won't add duplicate classes if a particular class was already set.
22804 *
22805 * When the expression changes, the previously added classes are removed and only then are the
22806 * new classes added.
22807 *
22808 * @animations
22809 * **add** - happens just before the class is applied to the elements
22810 *
22811 * **remove** - happens just before the class is removed from the element
22812 *
22813 * @element ANY
22814 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22815 * of the evaluation can be a string representing space delimited class
22816 * names, an array, or a map of class names to boolean values. In the case of a map, the
22817 * names of the properties whose values are truthy will be added as css classes to the
22818 * element.
22819 *
22820 * @example Example that demonstrates basic bindings via ngClass directive.
22821 <example>
22822 <file name="index.html">
22823 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22824 <label>
22825 <input type="checkbox" ng-model="deleted">
22826 deleted (apply "strike" class)
22827 </label><br>
22828 <label>
22829 <input type="checkbox" ng-model="important">
22830 important (apply "bold" class)
22831 </label><br>
22832 <label>
22833 <input type="checkbox" ng-model="error">
22834 error (apply "has-error" class)
22835 </label>
22836 <hr>
22837 <p ng-class="style">Using String Syntax</p>
22838 <input type="text" ng-model="style"
22839 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22840 <hr>
22841 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22842 <input ng-model="style1"
22843 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22844 <input ng-model="style2"
22845 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22846 <input ng-model="style3"
22847 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22848 <hr>
22849 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22850 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22851 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22852 </file>
22853 <file name="style.css">
22854 .strike {
22855 text-decoration: line-through;
22856 }
22857 .bold {
22858 font-weight: bold;
22859 }
22860 .red {
22861 color: red;
22862 }
22863 .has-error {
22864 color: red;
22865 background-color: yellow;
22866 }
22867 .orange {
22868 color: orange;
22869 }
22870 </file>
22871 <file name="protractor.js" type="protractor">
22872 var ps = element.all(by.css('p'));
22873
22874 it('should let you toggle the class', function() {
22875
22876 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22877 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22878
22879 element(by.model('important')).click();
22880 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22881
22882 element(by.model('error')).click();
22883 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22884 });
22885
22886 it('should let you toggle string example', function() {
22887 expect(ps.get(1).getAttribute('class')).toBe('');
22888 element(by.model('style')).clear();
22889 element(by.model('style')).sendKeys('red');
22890 expect(ps.get(1).getAttribute('class')).toBe('red');
22891 });
22892
22893 it('array example should have 3 classes', function() {
22894 expect(ps.get(2).getAttribute('class')).toBe('');
22895 element(by.model('style1')).sendKeys('bold');
22896 element(by.model('style2')).sendKeys('strike');
22897 element(by.model('style3')).sendKeys('red');
22898 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22899 });
22900
22901 it('array with map example should have 2 classes', function() {
22902 expect(ps.last().getAttribute('class')).toBe('');
22903 element(by.model('style4')).sendKeys('bold');
22904 element(by.model('warning')).click();
22905 expect(ps.last().getAttribute('class')).toBe('bold orange');
22906 });
22907 </file>
22908 </example>
22909
22910 ## Animations
22911
22912 The example below demonstrates how to perform animations using ngClass.
22913
22914 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22915 <file name="index.html">
22916 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22917 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22918 <br>
22919 <span class="base-class" ng-class="myVar">Sample Text</span>
22920 </file>
22921 <file name="style.css">
22922 .base-class {
22923 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
22924 }
22925
22926 .base-class.my-class {
22927 color: red;
22928 font-size:3em;
22929 }
22930 </file>
22931 <file name="protractor.js" type="protractor">
22932 it('should check ng-class', function() {
22933 expect(element(by.css('.base-class')).getAttribute('class')).not.
22934 toMatch(/my-class/);
22935
22936 element(by.id('setbtn')).click();
22937
22938 expect(element(by.css('.base-class')).getAttribute('class')).
22939 toMatch(/my-class/);
22940
22941 element(by.id('clearbtn')).click();
22942
22943 expect(element(by.css('.base-class')).getAttribute('class')).not.
22944 toMatch(/my-class/);
22945 });
22946 </file>
22947 </example>
22948
22949
22950 ## ngClass and pre-existing CSS3 Transitions/Animations
22951 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
22952 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
22953 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
22954 to view the step by step details of {@link $animate#addClass $animate.addClass} and
22955 {@link $animate#removeClass $animate.removeClass}.
22956 */
22957var ngClassDirective = classDirective('', true);
22958
22959/**
22960 * @ngdoc directive
22961 * @name ngClassOdd
22962 * @restrict AC
22963 *
22964 * @description
22965 * The `ngClassOdd` and `ngClassEven` directives work exactly as
22966 * {@link ng.directive:ngClass ngClass}, except they work in
22967 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
22968 *
22969 * This directive can be applied only within the scope of an
22970 * {@link ng.directive:ngRepeat ngRepeat}.
22971 *
22972 * @element ANY
22973 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
22974 * of the evaluation can be a string representing space delimited class names or an array.
22975 *
22976 * @example
22977 <example>
22978 <file name="index.html">
22979 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
22980 <li ng-repeat="name in names">
22981 <span ng-class-odd="'odd'" ng-class-even="'even'">
22982 {{name}}
22983 </span>
22984 </li>
22985 </ol>
22986 </file>
22987 <file name="style.css">
22988 .odd {
22989 color: red;
22990 }
22991 .even {
22992 color: blue;
22993 }
22994 </file>
22995 <file name="protractor.js" type="protractor">
22996 it('should check ng-class-odd and ng-class-even', function() {
22997 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
22998 toMatch(/odd/);
22999 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23000 toMatch(/even/);
23001 });
23002 </file>
23003 </example>
23004 */
23005var ngClassOddDirective = classDirective('Odd', 0);
23006
23007/**
23008 * @ngdoc directive
23009 * @name ngClassEven
23010 * @restrict AC
23011 *
23012 * @description
23013 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23014 * {@link ng.directive:ngClass ngClass}, except they work in
23015 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23016 *
23017 * This directive can be applied only within the scope of an
23018 * {@link ng.directive:ngRepeat ngRepeat}.
23019 *
23020 * @element ANY
23021 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23022 * result of the evaluation can be a string representing space delimited class names or an array.
23023 *
23024 * @example
23025 <example>
23026 <file name="index.html">
23027 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23028 <li ng-repeat="name in names">
23029 <span ng-class-odd="'odd'" ng-class-even="'even'">
23030 {{name}} &nbsp; &nbsp; &nbsp;
23031 </span>
23032 </li>
23033 </ol>
23034 </file>
23035 <file name="style.css">
23036 .odd {
23037 color: red;
23038 }
23039 .even {
23040 color: blue;
23041 }
23042 </file>
23043 <file name="protractor.js" type="protractor">
23044 it('should check ng-class-odd and ng-class-even', function() {
23045 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23046 toMatch(/odd/);
23047 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23048 toMatch(/even/);
23049 });
23050 </file>
23051 </example>
23052 */
23053var ngClassEvenDirective = classDirective('Even', 1);
23054
23055/**
23056 * @ngdoc directive
23057 * @name ngCloak
23058 * @restrict AC
23059 *
23060 * @description
23061 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23062 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23063 * directive to avoid the undesirable flicker effect caused by the html template display.
23064 *
23065 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23066 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23067 * of the browser view.
23068 *
23069 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23070 * `angular.min.js`.
23071 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23072 *
23073 * ```css
23074 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23075 * display: none !important;
23076 * }
23077 * ```
23078 *
23079 * When this css rule is loaded by the browser, all html elements (including their children) that
23080 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23081 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23082 * the compiled element visible.
23083 *
23084 * For the best result, the `angular.js` script must be loaded in the head section of the html
23085 * document; alternatively, the css rule above must be included in the external stylesheet of the
23086 * application.
23087 *
23088 * @element ANY
23089 *
23090 * @example
23091 <example>
23092 <file name="index.html">
23093 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23094 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23095 </file>
23096 <file name="protractor.js" type="protractor">
23097 it('should remove the template directive and css class', function() {
23098 expect($('#template1').getAttribute('ng-cloak')).
23099 toBeNull();
23100 expect($('#template2').getAttribute('ng-cloak')).
23101 toBeNull();
23102 });
23103 </file>
23104 </example>
23105 *
23106 */
23107var ngCloakDirective = ngDirective({
23108 compile: function(element, attr) {
23109 attr.$set('ngCloak', undefined);
23110 element.removeClass('ng-cloak');
23111 }
23112});
23113
23114/**
23115 * @ngdoc directive
23116 * @name ngController
23117 *
23118 * @description
23119 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23120 * supports the principles behind the Model-View-Controller design pattern.
23121 *
23122 * MVC components in angular:
23123 *
23124 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23125 * are accessed through bindings.
23126 * * View — The template (HTML with data bindings) that is rendered into the View.
23127 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23128 * logic behind the application to decorate the scope with functions and values
23129 *
23130 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23131 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23132 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23133 * and executed twice.
23134 *
23135 * @element ANY
23136 * @scope
23137 * @priority 500
23138 * @param {expression} ngController Name of a constructor function registered with the current
23139 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23140 * that on the current scope evaluates to a constructor function.
23141 *
23142 * The controller instance can be published into a scope property by specifying
23143 * `ng-controller="as propertyName"`.
23144 *
23145 * If the current `$controllerProvider` is configured to use globals (via
23146 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23147 * also be the name of a globally accessible constructor function (not recommended).
23148 *
23149 * @example
23150 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23151 * greeting are methods declared on the controller (see source tab). These methods can
23152 * easily be called from the angular markup. Any changes to the data are automatically reflected
23153 * in the View without the need for a manual update.
23154 *
23155 * Two different declaration styles are included below:
23156 *
23157 * * one binds methods and properties directly onto the controller using `this`:
23158 * `ng-controller="SettingsController1 as settings"`
23159 * * one injects `$scope` into the controller:
23160 * `ng-controller="SettingsController2"`
23161 *
23162 * The second option is more common in the Angular community, and is generally used in boilerplates
23163 * and in this guide. However, there are advantages to binding properties directly to the controller
23164 * and avoiding scope.
23165 *
23166 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23167 * multiple controllers apply to an element.
23168 * * If you are writing your controllers as classes you have easier access to the properties and
23169 * methods, which will appear on the scope, from inside the controller code.
23170 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23171 * inheritance masking primitives.
23172 *
23173 * This example demonstrates the `controller as` syntax.
23174 *
23175 * <example name="ngControllerAs" module="controllerAsExample">
23176 * <file name="index.html">
23177 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23178 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23179 * <button ng-click="settings.greet()">greet</button><br/>
23180 * Contact:
23181 * <ul>
23182 * <li ng-repeat="contact in settings.contacts">
23183 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23184 * <option>phone</option>
23185 * <option>email</option>
23186 * </select>
23187 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23188 * <button ng-click="settings.clearContact(contact)">clear</button>
23189 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23190 * </li>
23191 * <li><button ng-click="settings.addContact()">add</button></li>
23192 * </ul>
23193 * </div>
23194 * </file>
23195 * <file name="app.js">
23196 * angular.module('controllerAsExample', [])
23197 * .controller('SettingsController1', SettingsController1);
23198 *
23199 * function SettingsController1() {
23200 * this.name = "John Smith";
23201 * this.contacts = [
23202 * {type: 'phone', value: '408 555 1212'},
23203 * {type: 'email', value: 'john.smith@example.org'} ];
23204 * }
23205 *
23206 * SettingsController1.prototype.greet = function() {
23207 * alert(this.name);
23208 * };
23209 *
23210 * SettingsController1.prototype.addContact = function() {
23211 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23212 * };
23213 *
23214 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23215 * var index = this.contacts.indexOf(contactToRemove);
23216 * this.contacts.splice(index, 1);
23217 * };
23218 *
23219 * SettingsController1.prototype.clearContact = function(contact) {
23220 * contact.type = 'phone';
23221 * contact.value = '';
23222 * };
23223 * </file>
23224 * <file name="protractor.js" type="protractor">
23225 * it('should check controller as', function() {
23226 * var container = element(by.id('ctrl-as-exmpl'));
23227 * expect(container.element(by.model('settings.name'))
23228 * .getAttribute('value')).toBe('John Smith');
23229 *
23230 * var firstRepeat =
23231 * container.element(by.repeater('contact in settings.contacts').row(0));
23232 * var secondRepeat =
23233 * container.element(by.repeater('contact in settings.contacts').row(1));
23234 *
23235 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23236 * .toBe('408 555 1212');
23237 *
23238 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23239 * .toBe('john.smith@example.org');
23240 *
23241 * firstRepeat.element(by.buttonText('clear')).click();
23242 *
23243 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23244 * .toBe('');
23245 *
23246 * container.element(by.buttonText('add')).click();
23247 *
23248 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23249 * .element(by.model('contact.value'))
23250 * .getAttribute('value'))
23251 * .toBe('yourname@example.org');
23252 * });
23253 * </file>
23254 * </example>
23255 *
23256 * This example demonstrates the "attach to `$scope`" style of controller.
23257 *
23258 * <example name="ngController" module="controllerExample">
23259 * <file name="index.html">
23260 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23261 * <label>Name: <input type="text" ng-model="name"/></label>
23262 * <button ng-click="greet()">greet</button><br/>
23263 * Contact:
23264 * <ul>
23265 * <li ng-repeat="contact in contacts">
23266 * <select ng-model="contact.type" id="select_{{$index}}">
23267 * <option>phone</option>
23268 * <option>email</option>
23269 * </select>
23270 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23271 * <button ng-click="clearContact(contact)">clear</button>
23272 * <button ng-click="removeContact(contact)">X</button>
23273 * </li>
23274 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23275 * </ul>
23276 * </div>
23277 * </file>
23278 * <file name="app.js">
23279 * angular.module('controllerExample', [])
23280 * .controller('SettingsController2', ['$scope', SettingsController2]);
23281 *
23282 * function SettingsController2($scope) {
23283 * $scope.name = "John Smith";
23284 * $scope.contacts = [
23285 * {type:'phone', value:'408 555 1212'},
23286 * {type:'email', value:'john.smith@example.org'} ];
23287 *
23288 * $scope.greet = function() {
23289 * alert($scope.name);
23290 * };
23291 *
23292 * $scope.addContact = function() {
23293 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23294 * };
23295 *
23296 * $scope.removeContact = function(contactToRemove) {
23297 * var index = $scope.contacts.indexOf(contactToRemove);
23298 * $scope.contacts.splice(index, 1);
23299 * };
23300 *
23301 * $scope.clearContact = function(contact) {
23302 * contact.type = 'phone';
23303 * contact.value = '';
23304 * };
23305 * }
23306 * </file>
23307 * <file name="protractor.js" type="protractor">
23308 * it('should check controller', function() {
23309 * var container = element(by.id('ctrl-exmpl'));
23310 *
23311 * expect(container.element(by.model('name'))
23312 * .getAttribute('value')).toBe('John Smith');
23313 *
23314 * var firstRepeat =
23315 * container.element(by.repeater('contact in contacts').row(0));
23316 * var secondRepeat =
23317 * container.element(by.repeater('contact in contacts').row(1));
23318 *
23319 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23320 * .toBe('408 555 1212');
23321 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23322 * .toBe('john.smith@example.org');
23323 *
23324 * firstRepeat.element(by.buttonText('clear')).click();
23325 *
23326 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23327 * .toBe('');
23328 *
23329 * container.element(by.buttonText('add')).click();
23330 *
23331 * expect(container.element(by.repeater('contact in contacts').row(2))
23332 * .element(by.model('contact.value'))
23333 * .getAttribute('value'))
23334 * .toBe('yourname@example.org');
23335 * });
23336 * </file>
23337 *</example>
23338
23339 */
23340var ngControllerDirective = [function() {
23341 return {
23342 restrict: 'A',
23343 scope: true,
23344 controller: '@',
23345 priority: 500
23346 };
23347}];
23348
23349/**
23350 * @ngdoc directive
23351 * @name ngCsp
23352 *
23353 * @element html
23354 * @description
23355 *
23356 * Angular has some features that can break certain
23357 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23358 *
23359 * If you intend to implement these rules then you must tell Angular not to use these features.
23360 *
23361 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23362 *
23363 *
23364 * The following rules affect Angular:
23365 *
23366 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23367 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23368 * increase in the speed of evaluating Angular expressions.
23369 *
23370 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23371 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23372 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23373 * `angular-csp.css` in your HTML manually.
23374 *
23375 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23376 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23377 * however, triggers a CSP error to be logged in the console:
23378 *
23379 * ```
23380 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23381 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23382 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23383 * ```
23384 *
23385 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23386 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23387 * the `angular.js` file.
23388 *
23389 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23390 *
23391 * You can specify which of the CSP related Angular features should be deactivated by providing
23392 * a value for the `ng-csp` attribute. The options are as follows:
23393 *
23394 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23395 *
23396 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23397 *
23398 * You can use these values in the following combinations:
23399 *
23400 *
23401 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23402 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23403 * of Angular.
23404 *
23405 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23406 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23407 * of Angular.
23408 *
23409 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23410 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23411 *
23412 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23413 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23414 *
23415 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23416 * styles nor use eval, which is the same as an empty: ng-csp.
23417 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23418 *
23419 * @example
23420 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23421 ```html
23422 <!doctype html>
23423 <html ng-app ng-csp>
23424 ...
23425 ...
23426 </html>
23427 ```
23428 * @example
23429 // Note: the suffix `.csp` in the example name triggers
23430 // csp mode in our http server!
23431 <example name="example.csp" module="cspExample" ng-csp="true">
23432 <file name="index.html">
23433 <div ng-controller="MainController as ctrl">
23434 <div>
23435 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23436 <span id="counter">
23437 {{ctrl.counter}}
23438 </span>
23439 </div>
23440
23441 <div>
23442 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23443 <span id="evilError">
23444 {{ctrl.evilError}}
23445 </span>
23446 </div>
23447 </div>
23448 </file>
23449 <file name="script.js">
23450 angular.module('cspExample', [])
23451 .controller('MainController', function() {
23452 this.counter = 0;
23453 this.inc = function() {
23454 this.counter++;
23455 };
23456 this.evil = function() {
23457 // jshint evil:true
23458 try {
23459 eval('1+2');
23460 } catch (e) {
23461 this.evilError = e.message;
23462 }
23463 };
23464 });
23465 </file>
23466 <file name="protractor.js" type="protractor">
23467 var util, webdriver;
23468
23469 var incBtn = element(by.id('inc'));
23470 var counter = element(by.id('counter'));
23471 var evilBtn = element(by.id('evil'));
23472 var evilError = element(by.id('evilError'));
23473
23474 function getAndClearSevereErrors() {
23475 return browser.manage().logs().get('browser').then(function(browserLog) {
23476 return browserLog.filter(function(logEntry) {
23477 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23478 });
23479 });
23480 }
23481
23482 function clearErrors() {
23483 getAndClearSevereErrors();
23484 }
23485
23486 function expectNoErrors() {
23487 getAndClearSevereErrors().then(function(filteredLog) {
23488 expect(filteredLog.length).toEqual(0);
23489 if (filteredLog.length) {
23490 console.log('browser console errors: ' + util.inspect(filteredLog));
23491 }
23492 });
23493 }
23494
23495 function expectError(regex) {
23496 getAndClearSevereErrors().then(function(filteredLog) {
23497 var found = false;
23498 filteredLog.forEach(function(log) {
23499 if (log.message.match(regex)) {
23500 found = true;
23501 }
23502 });
23503 if (!found) {
23504 throw new Error('expected an error that matches ' + regex);
23505 }
23506 });
23507 }
23508
23509 beforeEach(function() {
23510 util = require('util');
23511 webdriver = require('protractor/node_modules/selenium-webdriver');
23512 });
23513
23514 // For now, we only test on Chrome,
23515 // as Safari does not load the page with Protractor's injected scripts,
23516 // and Firefox webdriver always disables content security policy (#6358)
23517 if (browser.params.browser !== 'chrome') {
23518 return;
23519 }
23520
23521 it('should not report errors when the page is loaded', function() {
23522 // clear errors so we are not dependent on previous tests
23523 clearErrors();
23524 // Need to reload the page as the page is already loaded when
23525 // we come here
23526 browser.driver.getCurrentUrl().then(function(url) {
23527 browser.get(url);
23528 });
23529 expectNoErrors();
23530 });
23531
23532 it('should evaluate expressions', function() {
23533 expect(counter.getText()).toEqual('0');
23534 incBtn.click();
23535 expect(counter.getText()).toEqual('1');
23536 expectNoErrors();
23537 });
23538
23539 it('should throw and report an error when using "eval"', function() {
23540 evilBtn.click();
23541 expect(evilError.getText()).toMatch(/Content Security Policy/);
23542 expectError(/Content Security Policy/);
23543 });
23544 </file>
23545 </example>
23546 */
23547
23548// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23549// bootstrap the system (before $parse is instantiated), for this reason we just have
23550// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23551
23552/**
23553 * @ngdoc directive
23554 * @name ngClick
23555 *
23556 * @description
23557 * The ngClick directive allows you to specify custom behavior when
23558 * an element is clicked.
23559 *
23560 * @element ANY
23561 * @priority 0
23562 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23563 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23564 *
23565 * @example
23566 <example>
23567 <file name="index.html">
23568 <button ng-click="count = count + 1" ng-init="count=0">
23569 Increment
23570 </button>
23571 <span>
23572 count: {{count}}
23573 </span>
23574 </file>
23575 <file name="protractor.js" type="protractor">
23576 it('should check ng-click', function() {
23577 expect(element(by.binding('count')).getText()).toMatch('0');
23578 element(by.css('button')).click();
23579 expect(element(by.binding('count')).getText()).toMatch('1');
23580 });
23581 </file>
23582 </example>
23583 */
23584/*
23585 * A collection of directives that allows creation of custom event handlers that are defined as
23586 * angular expressions and are compiled and executed within the current scope.
23587 */
23588var ngEventDirectives = {};
23589
23590// For events that might fire synchronously during DOM manipulation
23591// we need to execute their event handlers asynchronously using $evalAsync,
23592// so that they are not executed in an inconsistent state.
23593var forceAsyncEvents = {
23594 'blur': true,
23595 'focus': true
23596};
23597forEach(
23598 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23599 function(eventName) {
23600 var directiveName = directiveNormalize('ng-' + eventName);
23601 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23602 return {
23603 restrict: 'A',
23604 compile: function($element, attr) {
23605 // We expose the powerful $event object on the scope that provides access to the Window,
23606 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23607 // checks at the cost of speed since event handler expressions are not executed as
23608 // frequently as regular change detection.
23609 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23610 return function ngEventHandler(scope, element) {
23611 element.on(eventName, function(event) {
23612 var callback = function() {
23613 fn(scope, {$event:event});
23614 };
23615 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23616 scope.$evalAsync(callback);
23617 } else {
23618 scope.$apply(callback);
23619 }
23620 });
23621 };
23622 }
23623 };
23624 }];
23625 }
23626);
23627
23628/**
23629 * @ngdoc directive
23630 * @name ngDblclick
23631 *
23632 * @description
23633 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23634 *
23635 * @element ANY
23636 * @priority 0
23637 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23638 * a dblclick. (The Event object is available as `$event`)
23639 *
23640 * @example
23641 <example>
23642 <file name="index.html">
23643 <button ng-dblclick="count = count + 1" ng-init="count=0">
23644 Increment (on double click)
23645 </button>
23646 count: {{count}}
23647 </file>
23648 </example>
23649 */
23650
23651
23652/**
23653 * @ngdoc directive
23654 * @name ngMousedown
23655 *
23656 * @description
23657 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23658 *
23659 * @element ANY
23660 * @priority 0
23661 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23662 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23663 *
23664 * @example
23665 <example>
23666 <file name="index.html">
23667 <button ng-mousedown="count = count + 1" ng-init="count=0">
23668 Increment (on mouse down)
23669 </button>
23670 count: {{count}}
23671 </file>
23672 </example>
23673 */
23674
23675
23676/**
23677 * @ngdoc directive
23678 * @name ngMouseup
23679 *
23680 * @description
23681 * Specify custom behavior on mouseup event.
23682 *
23683 * @element ANY
23684 * @priority 0
23685 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23686 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23687 *
23688 * @example
23689 <example>
23690 <file name="index.html">
23691 <button ng-mouseup="count = count + 1" ng-init="count=0">
23692 Increment (on mouse up)
23693 </button>
23694 count: {{count}}
23695 </file>
23696 </example>
23697 */
23698
23699/**
23700 * @ngdoc directive
23701 * @name ngMouseover
23702 *
23703 * @description
23704 * Specify custom behavior on mouseover event.
23705 *
23706 * @element ANY
23707 * @priority 0
23708 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23709 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23710 *
23711 * @example
23712 <example>
23713 <file name="index.html">
23714 <button ng-mouseover="count = count + 1" ng-init="count=0">
23715 Increment (when mouse is over)
23716 </button>
23717 count: {{count}}
23718 </file>
23719 </example>
23720 */
23721
23722
23723/**
23724 * @ngdoc directive
23725 * @name ngMouseenter
23726 *
23727 * @description
23728 * Specify custom behavior on mouseenter event.
23729 *
23730 * @element ANY
23731 * @priority 0
23732 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23733 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23734 *
23735 * @example
23736 <example>
23737 <file name="index.html">
23738 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23739 Increment (when mouse enters)
23740 </button>
23741 count: {{count}}
23742 </file>
23743 </example>
23744 */
23745
23746
23747/**
23748 * @ngdoc directive
23749 * @name ngMouseleave
23750 *
23751 * @description
23752 * Specify custom behavior on mouseleave event.
23753 *
23754 * @element ANY
23755 * @priority 0
23756 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23757 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23758 *
23759 * @example
23760 <example>
23761 <file name="index.html">
23762 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23763 Increment (when mouse leaves)
23764 </button>
23765 count: {{count}}
23766 </file>
23767 </example>
23768 */
23769
23770
23771/**
23772 * @ngdoc directive
23773 * @name ngMousemove
23774 *
23775 * @description
23776 * Specify custom behavior on mousemove event.
23777 *
23778 * @element ANY
23779 * @priority 0
23780 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23781 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23782 *
23783 * @example
23784 <example>
23785 <file name="index.html">
23786 <button ng-mousemove="count = count + 1" ng-init="count=0">
23787 Increment (when mouse moves)
23788 </button>
23789 count: {{count}}
23790 </file>
23791 </example>
23792 */
23793
23794
23795/**
23796 * @ngdoc directive
23797 * @name ngKeydown
23798 *
23799 * @description
23800 * Specify custom behavior on keydown event.
23801 *
23802 * @element ANY
23803 * @priority 0
23804 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23805 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23806 *
23807 * @example
23808 <example>
23809 <file name="index.html">
23810 <input ng-keydown="count = count + 1" ng-init="count=0">
23811 key down count: {{count}}
23812 </file>
23813 </example>
23814 */
23815
23816
23817/**
23818 * @ngdoc directive
23819 * @name ngKeyup
23820 *
23821 * @description
23822 * Specify custom behavior on keyup event.
23823 *
23824 * @element ANY
23825 * @priority 0
23826 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23827 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23828 *
23829 * @example
23830 <example>
23831 <file name="index.html">
23832 <p>Typing in the input box below updates the key count</p>
23833 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23834
23835 <p>Typing in the input box below updates the keycode</p>
23836 <input ng-keyup="event=$event">
23837 <p>event keyCode: {{ event.keyCode }}</p>
23838 <p>event altKey: {{ event.altKey }}</p>
23839 </file>
23840 </example>
23841 */
23842
23843
23844/**
23845 * @ngdoc directive
23846 * @name ngKeypress
23847 *
23848 * @description
23849 * Specify custom behavior on keypress event.
23850 *
23851 * @element ANY
23852 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23853 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23854 * and can be interrogated for keyCode, altKey, etc.)
23855 *
23856 * @example
23857 <example>
23858 <file name="index.html">
23859 <input ng-keypress="count = count + 1" ng-init="count=0">
23860 key press count: {{count}}
23861 </file>
23862 </example>
23863 */
23864
23865
23866/**
23867 * @ngdoc directive
23868 * @name ngSubmit
23869 *
23870 * @description
23871 * Enables binding angular expressions to onsubmit events.
23872 *
23873 * Additionally it prevents the default action (which for form means sending the request to the
23874 * server and reloading the current page), but only if the form does not contain `action`,
23875 * `data-action`, or `x-action` attributes.
23876 *
23877 * <div class="alert alert-warning">
23878 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23879 * `ngSubmit` handlers together. See the
23880 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23881 * for a detailed discussion of when `ngSubmit` may be triggered.
23882 * </div>
23883 *
23884 * @element form
23885 * @priority 0
23886 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23887 * ({@link guide/expression#-event- Event object is available as `$event`})
23888 *
23889 * @example
23890 <example module="submitExample">
23891 <file name="index.html">
23892 <script>
23893 angular.module('submitExample', [])
23894 .controller('ExampleController', ['$scope', function($scope) {
23895 $scope.list = [];
23896 $scope.text = 'hello';
23897 $scope.submit = function() {
23898 if ($scope.text) {
23899 $scope.list.push(this.text);
23900 $scope.text = '';
23901 }
23902 };
23903 }]);
23904 </script>
23905 <form ng-submit="submit()" ng-controller="ExampleController">
23906 Enter text and hit enter:
23907 <input type="text" ng-model="text" name="text" />
23908 <input type="submit" id="submit" value="Submit" />
23909 <pre>list={{list}}</pre>
23910 </form>
23911 </file>
23912 <file name="protractor.js" type="protractor">
23913 it('should check ng-submit', function() {
23914 expect(element(by.binding('list')).getText()).toBe('list=[]');
23915 element(by.css('#submit')).click();
23916 expect(element(by.binding('list')).getText()).toContain('hello');
23917 expect(element(by.model('text')).getAttribute('value')).toBe('');
23918 });
23919 it('should ignore empty strings', function() {
23920 expect(element(by.binding('list')).getText()).toBe('list=[]');
23921 element(by.css('#submit')).click();
23922 element(by.css('#submit')).click();
23923 expect(element(by.binding('list')).getText()).toContain('hello');
23924 });
23925 </file>
23926 </example>
23927 */
23928
23929/**
23930 * @ngdoc directive
23931 * @name ngFocus
23932 *
23933 * @description
23934 * Specify custom behavior on focus event.
23935 *
23936 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
23937 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
23938 * during an `$apply` to ensure a consistent state.
23939 *
23940 * @element window, input, select, textarea, a
23941 * @priority 0
23942 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
23943 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
23944 *
23945 * @example
23946 * See {@link ng.directive:ngClick ngClick}
23947 */
23948
23949/**
23950 * @ngdoc directive
23951 * @name ngBlur
23952 *
23953 * @description
23954 * Specify custom behavior on blur event.
23955 *
23956 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
23957 * an element has lost focus.
23958 *
23959 * Note: As the `blur` event is executed synchronously also during DOM manipulations
23960 * (e.g. removing a focussed input),
23961 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
23962 * during an `$apply` to ensure a consistent state.
23963 *
23964 * @element window, input, select, textarea, a
23965 * @priority 0
23966 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
23967 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
23968 *
23969 * @example
23970 * See {@link ng.directive:ngClick ngClick}
23971 */
23972
23973/**
23974 * @ngdoc directive
23975 * @name ngCopy
23976 *
23977 * @description
23978 * Specify custom behavior on copy event.
23979 *
23980 * @element window, input, select, textarea, a
23981 * @priority 0
23982 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
23983 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
23984 *
23985 * @example
23986 <example>
23987 <file name="index.html">
23988 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
23989 copied: {{copied}}
23990 </file>
23991 </example>
23992 */
23993
23994/**
23995 * @ngdoc directive
23996 * @name ngCut
23997 *
23998 * @description
23999 * Specify custom behavior on cut event.
24000 *
24001 * @element window, input, select, textarea, a
24002 * @priority 0
24003 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24004 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24005 *
24006 * @example
24007 <example>
24008 <file name="index.html">
24009 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24010 cut: {{cut}}
24011 </file>
24012 </example>
24013 */
24014
24015/**
24016 * @ngdoc directive
24017 * @name ngPaste
24018 *
24019 * @description
24020 * Specify custom behavior on paste event.
24021 *
24022 * @element window, input, select, textarea, a
24023 * @priority 0
24024 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24025 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24026 *
24027 * @example
24028 <example>
24029 <file name="index.html">
24030 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24031 pasted: {{paste}}
24032 </file>
24033 </example>
24034 */
24035
24036/**
24037 * @ngdoc directive
24038 * @name ngIf
24039 * @restrict A
24040 * @multiElement
24041 *
24042 * @description
24043 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24044 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24045 * value then the element is removed from the DOM, otherwise a clone of the
24046 * element is reinserted into the DOM.
24047 *
24048 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24049 * element in the DOM rather than changing its visibility via the `display` css property. A common
24050 * case when this difference is significant is when using css selectors that rely on an element's
24051 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24052 *
24053 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24054 * is created when the element is restored. The scope created within `ngIf` inherits from
24055 * its parent scope using
24056 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24057 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24058 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24059 * variable within the child scope will override (hide) the value in the parent scope.
24060 *
24061 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24062 * is if an element's class attribute is directly modified after it's compiled, using something like
24063 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24064 * the added class will be lost because the original compiled state is used to regenerate the element.
24065 *
24066 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24067 * and `leave` effects.
24068 *
24069 * @animations
24070 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24071 * leave - happens just before the `ngIf` contents are removed from the DOM
24072 *
24073 * @element ANY
24074 * @scope
24075 * @priority 600
24076 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24077 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24078 * element is added to the DOM tree.
24079 *
24080 * @example
24081 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24082 <file name="index.html">
24083 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24084 Show when checked:
24085 <span ng-if="checked" class="animate-if">
24086 This is removed when the checkbox is unchecked.
24087 </span>
24088 </file>
24089 <file name="animations.css">
24090 .animate-if {
24091 background:white;
24092 border:1px solid black;
24093 padding:10px;
24094 }
24095
24096 .animate-if.ng-enter, .animate-if.ng-leave {
24097 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24098 }
24099
24100 .animate-if.ng-enter,
24101 .animate-if.ng-leave.ng-leave-active {
24102 opacity:0;
24103 }
24104
24105 .animate-if.ng-leave,
24106 .animate-if.ng-enter.ng-enter-active {
24107 opacity:1;
24108 }
24109 </file>
24110 </example>
24111 */
24112var ngIfDirective = ['$animate', function($animate) {
24113 return {
24114 multiElement: true,
24115 transclude: 'element',
24116 priority: 600,
24117 terminal: true,
24118 restrict: 'A',
24119 $$tlb: true,
24120 link: function($scope, $element, $attr, ctrl, $transclude) {
24121 var block, childScope, previousElements;
24122 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24123
24124 if (value) {
24125 if (!childScope) {
24126 $transclude(function(clone, newScope) {
24127 childScope = newScope;
24128 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24129 // Note: We only need the first/last node of the cloned nodes.
24130 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24131 // by a directive with templateUrl when its template arrives.
24132 block = {
24133 clone: clone
24134 };
24135 $animate.enter(clone, $element.parent(), $element);
24136 });
24137 }
24138 } else {
24139 if (previousElements) {
24140 previousElements.remove();
24141 previousElements = null;
24142 }
24143 if (childScope) {
24144 childScope.$destroy();
24145 childScope = null;
24146 }
24147 if (block) {
24148 previousElements = getBlockNodes(block.clone);
24149 $animate.leave(previousElements).then(function() {
24150 previousElements = null;
24151 });
24152 block = null;
24153 }
24154 }
24155 });
24156 }
24157 };
24158}];
24159
24160/**
24161 * @ngdoc directive
24162 * @name ngInclude
24163 * @restrict ECA
24164 *
24165 * @description
24166 * Fetches, compiles and includes an external HTML fragment.
24167 *
24168 * By default, the template URL is restricted to the same domain and protocol as the
24169 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24170 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24171 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24172 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24173 * ng.$sce Strict Contextual Escaping}.
24174 *
24175 * In addition, the browser's
24176 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24177 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24178 * policy may further restrict whether the template is successfully loaded.
24179 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24180 * access on some browsers.
24181 *
24182 * @animations
24183 * enter - animation is used to bring new content into the browser.
24184 * leave - animation is used to animate existing content away.
24185 *
24186 * The enter and leave animation occur concurrently.
24187 *
24188 * @scope
24189 * @priority 400
24190 *
24191 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24192 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24193 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24194 * <div class="alert alert-warning">
24195 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24196 * a function with the name on the window element, which will usually throw a
24197 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24198 * different form that {@link guide/directive#normalization matches} `onload`.
24199 * </div>
24200 *
24201 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24202 * $anchorScroll} to scroll the viewport after the content is loaded.
24203 *
24204 * - If the attribute is not set, disable scrolling.
24205 * - If the attribute is set without value, enable scrolling.
24206 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24207 *
24208 * @example
24209 <example module="includeExample" deps="angular-animate.js" animations="true">
24210 <file name="index.html">
24211 <div ng-controller="ExampleController">
24212 <select ng-model="template" ng-options="t.name for t in templates">
24213 <option value="">(blank)</option>
24214 </select>
24215 url of the template: <code>{{template.url}}</code>
24216 <hr/>
24217 <div class="slide-animate-container">
24218 <div class="slide-animate" ng-include="template.url"></div>
24219 </div>
24220 </div>
24221 </file>
24222 <file name="script.js">
24223 angular.module('includeExample', ['ngAnimate'])
24224 .controller('ExampleController', ['$scope', function($scope) {
24225 $scope.templates =
24226 [ { name: 'template1.html', url: 'template1.html'},
24227 { name: 'template2.html', url: 'template2.html'} ];
24228 $scope.template = $scope.templates[0];
24229 }]);
24230 </file>
24231 <file name="template1.html">
24232 Content of template1.html
24233 </file>
24234 <file name="template2.html">
24235 Content of template2.html
24236 </file>
24237 <file name="animations.css">
24238 .slide-animate-container {
24239 position:relative;
24240 background:white;
24241 border:1px solid black;
24242 height:40px;
24243 overflow:hidden;
24244 }
24245
24246 .slide-animate {
24247 padding:10px;
24248 }
24249
24250 .slide-animate.ng-enter, .slide-animate.ng-leave {
24251 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24252
24253 position:absolute;
24254 top:0;
24255 left:0;
24256 right:0;
24257 bottom:0;
24258 display:block;
24259 padding:10px;
24260 }
24261
24262 .slide-animate.ng-enter {
24263 top:-50px;
24264 }
24265 .slide-animate.ng-enter.ng-enter-active {
24266 top:0;
24267 }
24268
24269 .slide-animate.ng-leave {
24270 top:0;
24271 }
24272 .slide-animate.ng-leave.ng-leave-active {
24273 top:50px;
24274 }
24275 </file>
24276 <file name="protractor.js" type="protractor">
24277 var templateSelect = element(by.model('template'));
24278 var includeElem = element(by.css('[ng-include]'));
24279
24280 it('should load template1.html', function() {
24281 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24282 });
24283
24284 it('should load template2.html', function() {
24285 if (browser.params.browser == 'firefox') {
24286 // Firefox can't handle using selects
24287 // See https://github.com/angular/protractor/issues/480
24288 return;
24289 }
24290 templateSelect.click();
24291 templateSelect.all(by.css('option')).get(2).click();
24292 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24293 });
24294
24295 it('should change to blank', function() {
24296 if (browser.params.browser == 'firefox') {
24297 // Firefox can't handle using selects
24298 return;
24299 }
24300 templateSelect.click();
24301 templateSelect.all(by.css('option')).get(0).click();
24302 expect(includeElem.isPresent()).toBe(false);
24303 });
24304 </file>
24305 </example>
24306 */
24307
24308
24309/**
24310 * @ngdoc event
24311 * @name ngInclude#$includeContentRequested
24312 * @eventType emit on the scope ngInclude was declared in
24313 * @description
24314 * Emitted every time the ngInclude content is requested.
24315 *
24316 * @param {Object} angularEvent Synthetic event object.
24317 * @param {String} src URL of content to load.
24318 */
24319
24320
24321/**
24322 * @ngdoc event
24323 * @name ngInclude#$includeContentLoaded
24324 * @eventType emit on the current ngInclude scope
24325 * @description
24326 * Emitted every time the ngInclude content is reloaded.
24327 *
24328 * @param {Object} angularEvent Synthetic event object.
24329 * @param {String} src URL of content to load.
24330 */
24331
24332
24333/**
24334 * @ngdoc event
24335 * @name ngInclude#$includeContentError
24336 * @eventType emit on the scope ngInclude was declared in
24337 * @description
24338 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24339 *
24340 * @param {Object} angularEvent Synthetic event object.
24341 * @param {String} src URL of content to load.
24342 */
24343var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24344 function($templateRequest, $anchorScroll, $animate) {
24345 return {
24346 restrict: 'ECA',
24347 priority: 400,
24348 terminal: true,
24349 transclude: 'element',
24350 controller: angular.noop,
24351 compile: function(element, attr) {
24352 var srcExp = attr.ngInclude || attr.src,
24353 onloadExp = attr.onload || '',
24354 autoScrollExp = attr.autoscroll;
24355
24356 return function(scope, $element, $attr, ctrl, $transclude) {
24357 var changeCounter = 0,
24358 currentScope,
24359 previousElement,
24360 currentElement;
24361
24362 var cleanupLastIncludeContent = function() {
24363 if (previousElement) {
24364 previousElement.remove();
24365 previousElement = null;
24366 }
24367 if (currentScope) {
24368 currentScope.$destroy();
24369 currentScope = null;
24370 }
24371 if (currentElement) {
24372 $animate.leave(currentElement).then(function() {
24373 previousElement = null;
24374 });
24375 previousElement = currentElement;
24376 currentElement = null;
24377 }
24378 };
24379
24380 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24381 var afterAnimation = function() {
24382 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24383 $anchorScroll();
24384 }
24385 };
24386 var thisChangeId = ++changeCounter;
24387
24388 if (src) {
24389 //set the 2nd param to true to ignore the template request error so that the inner
24390 //contents and scope can be cleaned up.
24391 $templateRequest(src, true).then(function(response) {
24392 if (thisChangeId !== changeCounter) return;
24393 var newScope = scope.$new();
24394 ctrl.template = response;
24395
24396 // Note: This will also link all children of ng-include that were contained in the original
24397 // html. If that content contains controllers, ... they could pollute/change the scope.
24398 // However, using ng-include on an element with additional content does not make sense...
24399 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24400 // function is called before linking the content, which would apply child
24401 // directives to non existing elements.
24402 var clone = $transclude(newScope, function(clone) {
24403 cleanupLastIncludeContent();
24404 $animate.enter(clone, null, $element).then(afterAnimation);
24405 });
24406
24407 currentScope = newScope;
24408 currentElement = clone;
24409
24410 currentScope.$emit('$includeContentLoaded', src);
24411 scope.$eval(onloadExp);
24412 }, function() {
24413 if (thisChangeId === changeCounter) {
24414 cleanupLastIncludeContent();
24415 scope.$emit('$includeContentError', src);
24416 }
24417 });
24418 scope.$emit('$includeContentRequested', src);
24419 } else {
24420 cleanupLastIncludeContent();
24421 ctrl.template = null;
24422 }
24423 });
24424 };
24425 }
24426 };
24427}];
24428
24429// This directive is called during the $transclude call of the first `ngInclude` directive.
24430// It will replace and compile the content of the element with the loaded template.
24431// We need this directive so that the element content is already filled when
24432// the link function of another directive on the same element as ngInclude
24433// is called.
24434var ngIncludeFillContentDirective = ['$compile',
24435 function($compile) {
24436 return {
24437 restrict: 'ECA',
24438 priority: -400,
24439 require: 'ngInclude',
24440 link: function(scope, $element, $attr, ctrl) {
24441 if (/SVG/.test($element[0].toString())) {
24442 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24443 // support innerHTML, so detect this here and try to generate the contents
24444 // specially.
24445 $element.empty();
24446 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24447 function namespaceAdaptedClone(clone) {
24448 $element.append(clone);
24449 }, {futureParentElement: $element});
24450 return;
24451 }
24452
24453 $element.html(ctrl.template);
24454 $compile($element.contents())(scope);
24455 }
24456 };
24457 }];
24458
24459/**
24460 * @ngdoc directive
24461 * @name ngInit
24462 * @restrict AC
24463 *
24464 * @description
24465 * The `ngInit` directive allows you to evaluate an expression in the
24466 * current scope.
24467 *
24468 * <div class="alert alert-danger">
24469 * This directive can be abused to add unnecessary amounts of logic into your templates.
24470 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24471 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24472 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24473 * rather than `ngInit` to initialize values on a scope.
24474 * </div>
24475 *
24476 * <div class="alert alert-warning">
24477 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24478 * sure you have parentheses to ensure correct operator precedence:
24479 * <pre class="prettyprint">
24480 * `<div ng-init="test1 = ($index | toString)"></div>`
24481 * </pre>
24482 * </div>
24483 *
24484 * @priority 450
24485 *
24486 * @element ANY
24487 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24488 *
24489 * @example
24490 <example module="initExample">
24491 <file name="index.html">
24492 <script>
24493 angular.module('initExample', [])
24494 .controller('ExampleController', ['$scope', function($scope) {
24495 $scope.list = [['a', 'b'], ['c', 'd']];
24496 }]);
24497 </script>
24498 <div ng-controller="ExampleController">
24499 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24500 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24501 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24502 </div>
24503 </div>
24504 </div>
24505 </file>
24506 <file name="protractor.js" type="protractor">
24507 it('should alias index positions', function() {
24508 var elements = element.all(by.css('.example-init'));
24509 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24510 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24511 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24512 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24513 });
24514 </file>
24515 </example>
24516 */
24517var ngInitDirective = ngDirective({
24518 priority: 450,
24519 compile: function() {
24520 return {
24521 pre: function(scope, element, attrs) {
24522 scope.$eval(attrs.ngInit);
24523 }
24524 };
24525 }
24526});
24527
24528/**
24529 * @ngdoc directive
24530 * @name ngList
24531 *
24532 * @description
24533 * Text input that converts between a delimited string and an array of strings. The default
24534 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24535 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24536 *
24537 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24538 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24539 * list item is respected. This implies that the user of the directive is responsible for
24540 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24541 * tab or newline character.
24542 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24543 * when joining the list items back together) and whitespace around each list item is stripped
24544 * before it is added to the model.
24545 *
24546 * ### Example with Validation
24547 *
24548 * <example name="ngList-directive" module="listExample">
24549 * <file name="app.js">
24550 * angular.module('listExample', [])
24551 * .controller('ExampleController', ['$scope', function($scope) {
24552 * $scope.names = ['morpheus', 'neo', 'trinity'];
24553 * }]);
24554 * </file>
24555 * <file name="index.html">
24556 * <form name="myForm" ng-controller="ExampleController">
24557 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24558 * <span role="alert">
24559 * <span class="error" ng-show="myForm.namesInput.$error.required">
24560 * Required!</span>
24561 * </span>
24562 * <br>
24563 * <tt>names = {{names}}</tt><br/>
24564 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24565 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24566 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24567 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24568 * </form>
24569 * </file>
24570 * <file name="protractor.js" type="protractor">
24571 * var listInput = element(by.model('names'));
24572 * var names = element(by.exactBinding('names'));
24573 * var valid = element(by.binding('myForm.namesInput.$valid'));
24574 * var error = element(by.css('span.error'));
24575 *
24576 * it('should initialize to model', function() {
24577 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24578 * expect(valid.getText()).toContain('true');
24579 * expect(error.getCssValue('display')).toBe('none');
24580 * });
24581 *
24582 * it('should be invalid if empty', function() {
24583 * listInput.clear();
24584 * listInput.sendKeys('');
24585 *
24586 * expect(names.getText()).toContain('');
24587 * expect(valid.getText()).toContain('false');
24588 * expect(error.getCssValue('display')).not.toBe('none');
24589 * });
24590 * </file>
24591 * </example>
24592 *
24593 * ### Example - splitting on newline
24594 * <example name="ngList-directive-newlines">
24595 * <file name="index.html">
24596 * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
24597 * <pre>{{ list | json }}</pre>
24598 * </file>
24599 * <file name="protractor.js" type="protractor">
24600 * it("should split the text by newlines", function() {
24601 * var listInput = element(by.model('list'));
24602 * var output = element(by.binding('list | json'));
24603 * listInput.sendKeys('abc\ndef\nghi');
24604 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24605 * });
24606 * </file>
24607 * </example>
24608 *
24609 * @element input
24610 * @param {string=} ngList optional delimiter that should be used to split the value.
24611 */
24612var ngListDirective = function() {
24613 return {
24614 restrict: 'A',
24615 priority: 100,
24616 require: 'ngModel',
24617 link: function(scope, element, attr, ctrl) {
24618 // We want to control whitespace trimming so we use this convoluted approach
24619 // to access the ngList attribute, which doesn't pre-trim the attribute
24620 var ngList = element.attr(attr.$attr.ngList) || ', ';
24621 var trimValues = attr.ngTrim !== 'false';
24622 var separator = trimValues ? trim(ngList) : ngList;
24623
24624 var parse = function(viewValue) {
24625 // If the viewValue is invalid (say required but empty) it will be `undefined`
24626 if (isUndefined(viewValue)) return;
24627
24628 var list = [];
24629
24630 if (viewValue) {
24631 forEach(viewValue.split(separator), function(value) {
24632 if (value) list.push(trimValues ? trim(value) : value);
24633 });
24634 }
24635
24636 return list;
24637 };
24638
24639 ctrl.$parsers.push(parse);
24640 ctrl.$formatters.push(function(value) {
24641 if (isArray(value)) {
24642 return value.join(ngList);
24643 }
24644
24645 return undefined;
24646 });
24647
24648 // Override the standard $isEmpty because an empty array means the input is empty.
24649 ctrl.$isEmpty = function(value) {
24650 return !value || !value.length;
24651 };
24652 }
24653 };
24654};
24655
24656/* global VALID_CLASS: true,
24657 INVALID_CLASS: true,
24658 PRISTINE_CLASS: true,
24659 DIRTY_CLASS: true,
24660 UNTOUCHED_CLASS: true,
24661 TOUCHED_CLASS: true,
24662*/
24663
24664var VALID_CLASS = 'ng-valid',
24665 INVALID_CLASS = 'ng-invalid',
24666 PRISTINE_CLASS = 'ng-pristine',
24667 DIRTY_CLASS = 'ng-dirty',
24668 UNTOUCHED_CLASS = 'ng-untouched',
24669 TOUCHED_CLASS = 'ng-touched',
24670 PENDING_CLASS = 'ng-pending';
24671
24672var ngModelMinErr = minErr('ngModel');
24673
24674/**
24675 * @ngdoc type
24676 * @name ngModel.NgModelController
24677 *
24678 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24679 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24680 * is set.
24681 * @property {*} $modelValue The value in the model that the control is bound to.
24682 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24683 the control reads value from the DOM. The functions are called in array order, each passing
24684 its return value through to the next. The last return value is forwarded to the
24685 {@link ngModel.NgModelController#$validators `$validators`} collection.
24686
24687Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24688`$viewValue`}.
24689
24690Returning `undefined` from a parser means a parse error occurred. In that case,
24691no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24692will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24693is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24694
24695 *
24696 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24697 the model value changes. The functions are called in reverse array order, each passing the value through to the
24698 next. The last return value is used as the actual DOM value.
24699 Used to format / convert values for display in the control.
24700 * ```js
24701 * function formatter(value) {
24702 * if (value) {
24703 * return value.toUpperCase();
24704 * }
24705 * }
24706 * ngModel.$formatters.push(formatter);
24707 * ```
24708 *
24709 * @property {Object.<string, function>} $validators A collection of validators that are applied
24710 * whenever the model value changes. The key value within the object refers to the name of the
24711 * validator while the function refers to the validation operation. The validation operation is
24712 * provided with the model value as an argument and must return a true or false value depending
24713 * on the response of that validation.
24714 *
24715 * ```js
24716 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24717 * var value = modelValue || viewValue;
24718 * return /[0-9]+/.test(value) &&
24719 * /[a-z]+/.test(value) &&
24720 * /[A-Z]+/.test(value) &&
24721 * /\W+/.test(value);
24722 * };
24723 * ```
24724 *
24725 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24726 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24727 * is expected to return a promise when it is run during the model validation process. Once the promise
24728 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24729 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24730 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24731 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24732 * will only run once all synchronous validators have passed.
24733 *
24734 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24735 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24736 *
24737 * ```js
24738 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24739 * var value = modelValue || viewValue;
24740 *
24741 * // Lookup user by username
24742 * return $http.get('/api/users/' + value).
24743 * then(function resolved() {
24744 * //username exists, this means validation fails
24745 * return $q.reject('exists');
24746 * }, function rejected() {
24747 * //username does not exist, therefore this validation passes
24748 * return true;
24749 * });
24750 * };
24751 * ```
24752 *
24753 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24754 * view value has changed. It is called with no arguments, and its return value is ignored.
24755 * This can be used in place of additional $watches against the model value.
24756 *
24757 * @property {Object} $error An object hash with all failing validator ids as keys.
24758 * @property {Object} $pending An object hash with all pending validator ids as keys.
24759 *
24760 * @property {boolean} $untouched True if control has not lost focus yet.
24761 * @property {boolean} $touched True if control has lost focus.
24762 * @property {boolean} $pristine True if user has not interacted with the control yet.
24763 * @property {boolean} $dirty True if user has already interacted with the control.
24764 * @property {boolean} $valid True if there is no error.
24765 * @property {boolean} $invalid True if at least one error on the control.
24766 * @property {string} $name The name attribute of the control.
24767 *
24768 * @description
24769 *
24770 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24771 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24772 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24773 * listening to DOM events.
24774 * Such DOM related logic should be provided by other directives which make use of
24775 * `NgModelController` for data-binding to control elements.
24776 * Angular provides this DOM logic for most {@link input `input`} elements.
24777 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24778 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24779 *
24780 * @example
24781 * ### Custom Control Example
24782 * This example shows how to use `NgModelController` with a custom control to achieve
24783 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24784 * collaborate together to achieve the desired result.
24785 *
24786 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24787 * contents be edited in place by the user.
24788 *
24789 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24790 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24791 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24792 * that content using the `$sce` service.
24793 *
24794 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24795 <file name="style.css">
24796 [contenteditable] {
24797 border: 1px solid black;
24798 background-color: white;
24799 min-height: 20px;
24800 }
24801
24802 .ng-invalid {
24803 border: 1px solid red;
24804 }
24805
24806 </file>
24807 <file name="script.js">
24808 angular.module('customControl', ['ngSanitize']).
24809 directive('contenteditable', ['$sce', function($sce) {
24810 return {
24811 restrict: 'A', // only activate on element attribute
24812 require: '?ngModel', // get a hold of NgModelController
24813 link: function(scope, element, attrs, ngModel) {
24814 if (!ngModel) return; // do nothing if no ng-model
24815
24816 // Specify how UI should be updated
24817 ngModel.$render = function() {
24818 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24819 };
24820
24821 // Listen for change events to enable binding
24822 element.on('blur keyup change', function() {
24823 scope.$evalAsync(read);
24824 });
24825 read(); // initialize
24826
24827 // Write data to the model
24828 function read() {
24829 var html = element.html();
24830 // When we clear the content editable the browser leaves a <br> behind
24831 // If strip-br attribute is provided then we strip this out
24832 if ( attrs.stripBr && html == '<br>' ) {
24833 html = '';
24834 }
24835 ngModel.$setViewValue(html);
24836 }
24837 }
24838 };
24839 }]);
24840 </file>
24841 <file name="index.html">
24842 <form name="myForm">
24843 <div contenteditable
24844 name="myWidget" ng-model="userContent"
24845 strip-br="true"
24846 required>Change me!</div>
24847 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24848 <hr>
24849 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24850 </form>
24851 </file>
24852 <file name="protractor.js" type="protractor">
24853 it('should data-bind and become invalid', function() {
24854 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24855 // SafariDriver can't handle contenteditable
24856 // and Firefox driver can't clear contenteditables very well
24857 return;
24858 }
24859 var contentEditable = element(by.css('[contenteditable]'));
24860 var content = 'Change me!';
24861
24862 expect(contentEditable.getText()).toEqual(content);
24863
24864 contentEditable.clear();
24865 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24866 expect(contentEditable.getText()).toEqual('');
24867 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24868 });
24869 </file>
24870 * </example>
24871 *
24872 *
24873 */
24874var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24875 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24876 this.$viewValue = Number.NaN;
24877 this.$modelValue = Number.NaN;
24878 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24879 this.$validators = {};
24880 this.$asyncValidators = {};
24881 this.$parsers = [];
24882 this.$formatters = [];
24883 this.$viewChangeListeners = [];
24884 this.$untouched = true;
24885 this.$touched = false;
24886 this.$pristine = true;
24887 this.$dirty = false;
24888 this.$valid = true;
24889 this.$invalid = false;
24890 this.$error = {}; // keep invalid keys here
24891 this.$$success = {}; // keep valid keys here
24892 this.$pending = undefined; // keep pending keys here
24893 this.$name = $interpolate($attr.name || '', false)($scope);
24894 this.$$parentForm = nullFormCtrl;
24895
24896 var parsedNgModel = $parse($attr.ngModel),
24897 parsedNgModelAssign = parsedNgModel.assign,
24898 ngModelGet = parsedNgModel,
24899 ngModelSet = parsedNgModelAssign,
24900 pendingDebounce = null,
24901 parserValid,
24902 ctrl = this;
24903
24904 this.$$setOptions = function(options) {
24905 ctrl.$options = options;
24906 if (options && options.getterSetter) {
24907 var invokeModelGetter = $parse($attr.ngModel + '()'),
24908 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24909
24910 ngModelGet = function($scope) {
24911 var modelValue = parsedNgModel($scope);
24912 if (isFunction(modelValue)) {
24913 modelValue = invokeModelGetter($scope);
24914 }
24915 return modelValue;
24916 };
24917 ngModelSet = function($scope, newValue) {
24918 if (isFunction(parsedNgModel($scope))) {
24919 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
24920 } else {
24921 parsedNgModelAssign($scope, ctrl.$modelValue);
24922 }
24923 };
24924 } else if (!parsedNgModel.assign) {
24925 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
24926 $attr.ngModel, startingTag($element));
24927 }
24928 };
24929
24930 /**
24931 * @ngdoc method
24932 * @name ngModel.NgModelController#$render
24933 *
24934 * @description
24935 * Called when the view needs to be updated. It is expected that the user of the ng-model
24936 * directive will implement this method.
24937 *
24938 * The `$render()` method is invoked in the following situations:
24939 *
24940 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
24941 * committed value then `$render()` is called to update the input control.
24942 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
24943 * the `$viewValue` are different from last time.
24944 *
24945 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
24946 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
24947 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
24948 * invoked if you only change a property on the objects.
24949 */
24950 this.$render = noop;
24951
24952 /**
24953 * @ngdoc method
24954 * @name ngModel.NgModelController#$isEmpty
24955 *
24956 * @description
24957 * This is called when we need to determine if the value of an input is empty.
24958 *
24959 * For instance, the required directive does this to work out if the input has data or not.
24960 *
24961 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
24962 *
24963 * You can override this for input directives whose concept of being empty is different from the
24964 * default. The `checkboxInputType` directive does this because in its case a value of `false`
24965 * implies empty.
24966 *
24967 * @param {*} value The value of the input to check for emptiness.
24968 * @returns {boolean} True if `value` is "empty".
24969 */
24970 this.$isEmpty = function(value) {
24971 return isUndefined(value) || value === '' || value === null || value !== value;
24972 };
24973
24974 var currentValidationRunId = 0;
24975
24976 /**
24977 * @ngdoc method
24978 * @name ngModel.NgModelController#$setValidity
24979 *
24980 * @description
24981 * Change the validity state, and notify the form.
24982 *
24983 * This method can be called within $parsers/$formatters or a custom validation implementation.
24984 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
24985 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
24986 *
24987 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
24988 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
24989 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
24990 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
24991 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
24992 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
24993 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
24994 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
24995 * Skipped is used by Angular when validators do not run because of parse errors and
24996 * when `$asyncValidators` do not run because any of the `$validators` failed.
24997 */
24998 addSetValidityMethod({
24999 ctrl: this,
25000 $element: $element,
25001 set: function(object, property) {
25002 object[property] = true;
25003 },
25004 unset: function(object, property) {
25005 delete object[property];
25006 },
25007 $animate: $animate
25008 });
25009
25010 /**
25011 * @ngdoc method
25012 * @name ngModel.NgModelController#$setPristine
25013 *
25014 * @description
25015 * Sets the control to its pristine state.
25016 *
25017 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25018 * state (`ng-pristine` class). A model is considered to be pristine when the control
25019 * has not been changed from when first compiled.
25020 */
25021 this.$setPristine = function() {
25022 ctrl.$dirty = false;
25023 ctrl.$pristine = true;
25024 $animate.removeClass($element, DIRTY_CLASS);
25025 $animate.addClass($element, PRISTINE_CLASS);
25026 };
25027
25028 /**
25029 * @ngdoc method
25030 * @name ngModel.NgModelController#$setDirty
25031 *
25032 * @description
25033 * Sets the control to its dirty state.
25034 *
25035 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25036 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25037 * from when first compiled.
25038 */
25039 this.$setDirty = function() {
25040 ctrl.$dirty = true;
25041 ctrl.$pristine = false;
25042 $animate.removeClass($element, PRISTINE_CLASS);
25043 $animate.addClass($element, DIRTY_CLASS);
25044 ctrl.$$parentForm.$setDirty();
25045 };
25046
25047 /**
25048 * @ngdoc method
25049 * @name ngModel.NgModelController#$setUntouched
25050 *
25051 * @description
25052 * Sets the control to its untouched state.
25053 *
25054 * This method can be called to remove the `ng-touched` class and set the control to its
25055 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25056 * by default, however this function can be used to restore that state if the model has
25057 * already been touched by the user.
25058 */
25059 this.$setUntouched = function() {
25060 ctrl.$touched = false;
25061 ctrl.$untouched = true;
25062 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25063 };
25064
25065 /**
25066 * @ngdoc method
25067 * @name ngModel.NgModelController#$setTouched
25068 *
25069 * @description
25070 * Sets the control to its touched state.
25071 *
25072 * This method can be called to remove the `ng-untouched` class and set the control to its
25073 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25074 * first focused the control element and then shifted focus away from the control (blur event).
25075 */
25076 this.$setTouched = function() {
25077 ctrl.$touched = true;
25078 ctrl.$untouched = false;
25079 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25080 };
25081
25082 /**
25083 * @ngdoc method
25084 * @name ngModel.NgModelController#$rollbackViewValue
25085 *
25086 * @description
25087 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25088 * which may be caused by a pending debounced event or because the input is waiting for a some
25089 * future event.
25090 *
25091 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25092 * as blur you can have a situation where there is a period when the `$viewValue`
25093 * is out of synch with the ngModel's `$modelValue`.
25094 *
25095 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25096 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25097 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25098 *
25099 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25100 * input which may have such events pending. This is important in order to make sure that the
25101 * input field will be updated with the new model value and any pending operations are cancelled.
25102 *
25103 * <example name="ng-model-cancel-update" module="cancel-update-example">
25104 * <file name="app.js">
25105 * angular.module('cancel-update-example', [])
25106 *
25107 * .controller('CancelUpdateController', ['$scope', function($scope) {
25108 * $scope.resetWithCancel = function(e) {
25109 * if (e.keyCode == 27) {
25110 * $scope.myForm.myInput1.$rollbackViewValue();
25111 * $scope.myValue = '';
25112 * }
25113 * };
25114 * $scope.resetWithoutCancel = function(e) {
25115 * if (e.keyCode == 27) {
25116 * $scope.myValue = '';
25117 * }
25118 * };
25119 * }]);
25120 * </file>
25121 * <file name="index.html">
25122 * <div ng-controller="CancelUpdateController">
25123 * <p>Try typing something in each input. See that the model only updates when you
25124 * blur off the input.
25125 * </p>
25126 * <p>Now see what happens if you start typing then press the Escape key</p>
25127 *
25128 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25129 * <p id="inputDescription1">With $rollbackViewValue()</p>
25130 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25131 * ng-keydown="resetWithCancel($event)"><br/>
25132 * myValue: "{{ myValue }}"
25133 *
25134 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25135 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25136 * ng-keydown="resetWithoutCancel($event)"><br/>
25137 * myValue: "{{ myValue }}"
25138 * </form>
25139 * </div>
25140 * </file>
25141 * </example>
25142 */
25143 this.$rollbackViewValue = function() {
25144 $timeout.cancel(pendingDebounce);
25145 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25146 ctrl.$render();
25147 };
25148
25149 /**
25150 * @ngdoc method
25151 * @name ngModel.NgModelController#$validate
25152 *
25153 * @description
25154 * Runs each of the registered validators (first synchronous validators and then
25155 * asynchronous validators).
25156 * If the validity changes to invalid, the model will be set to `undefined`,
25157 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25158 * If the validity changes to valid, it will set the model to the last available valid
25159 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25160 */
25161 this.$validate = function() {
25162 // ignore $validate before model is initialized
25163 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25164 return;
25165 }
25166
25167 var viewValue = ctrl.$$lastCommittedViewValue;
25168 // Note: we use the $$rawModelValue as $modelValue might have been
25169 // set to undefined during a view -> model update that found validation
25170 // errors. We can't parse the view here, since that could change
25171 // the model although neither viewValue nor the model on the scope changed
25172 var modelValue = ctrl.$$rawModelValue;
25173
25174 var prevValid = ctrl.$valid;
25175 var prevModelValue = ctrl.$modelValue;
25176
25177 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25178
25179 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25180 // If there was no change in validity, don't update the model
25181 // This prevents changing an invalid modelValue to undefined
25182 if (!allowInvalid && prevValid !== allValid) {
25183 // Note: Don't check ctrl.$valid here, as we could have
25184 // external validators (e.g. calculated on the server),
25185 // that just call $setValidity and need the model value
25186 // to calculate their validity.
25187 ctrl.$modelValue = allValid ? modelValue : undefined;
25188
25189 if (ctrl.$modelValue !== prevModelValue) {
25190 ctrl.$$writeModelToScope();
25191 }
25192 }
25193 });
25194
25195 };
25196
25197 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25198 currentValidationRunId++;
25199 var localValidationRunId = currentValidationRunId;
25200
25201 // check parser error
25202 if (!processParseErrors()) {
25203 validationDone(false);
25204 return;
25205 }
25206 if (!processSyncValidators()) {
25207 validationDone(false);
25208 return;
25209 }
25210 processAsyncValidators();
25211
25212 function processParseErrors() {
25213 var errorKey = ctrl.$$parserName || 'parse';
25214 if (isUndefined(parserValid)) {
25215 setValidity(errorKey, null);
25216 } else {
25217 if (!parserValid) {
25218 forEach(ctrl.$validators, function(v, name) {
25219 setValidity(name, null);
25220 });
25221 forEach(ctrl.$asyncValidators, function(v, name) {
25222 setValidity(name, null);
25223 });
25224 }
25225 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25226 setValidity(errorKey, parserValid);
25227 return parserValid;
25228 }
25229 return true;
25230 }
25231
25232 function processSyncValidators() {
25233 var syncValidatorsValid = true;
25234 forEach(ctrl.$validators, function(validator, name) {
25235 var result = validator(modelValue, viewValue);
25236 syncValidatorsValid = syncValidatorsValid && result;
25237 setValidity(name, result);
25238 });
25239 if (!syncValidatorsValid) {
25240 forEach(ctrl.$asyncValidators, function(v, name) {
25241 setValidity(name, null);
25242 });
25243 return false;
25244 }
25245 return true;
25246 }
25247
25248 function processAsyncValidators() {
25249 var validatorPromises = [];
25250 var allValid = true;
25251 forEach(ctrl.$asyncValidators, function(validator, name) {
25252 var promise = validator(modelValue, viewValue);
25253 if (!isPromiseLike(promise)) {
25254 throw ngModelMinErr("$asyncValidators",
25255 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25256 }
25257 setValidity(name, undefined);
25258 validatorPromises.push(promise.then(function() {
25259 setValidity(name, true);
25260 }, function(error) {
25261 allValid = false;
25262 setValidity(name, false);
25263 }));
25264 });
25265 if (!validatorPromises.length) {
25266 validationDone(true);
25267 } else {
25268 $q.all(validatorPromises).then(function() {
25269 validationDone(allValid);
25270 }, noop);
25271 }
25272 }
25273
25274 function setValidity(name, isValid) {
25275 if (localValidationRunId === currentValidationRunId) {
25276 ctrl.$setValidity(name, isValid);
25277 }
25278 }
25279
25280 function validationDone(allValid) {
25281 if (localValidationRunId === currentValidationRunId) {
25282
25283 doneCallback(allValid);
25284 }
25285 }
25286 };
25287
25288 /**
25289 * @ngdoc method
25290 * @name ngModel.NgModelController#$commitViewValue
25291 *
25292 * @description
25293 * Commit a pending update to the `$modelValue`.
25294 *
25295 * Updates may be pending by a debounced event or because the input is waiting for a some future
25296 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25297 * usually handles calling this in response to input events.
25298 */
25299 this.$commitViewValue = function() {
25300 var viewValue = ctrl.$viewValue;
25301
25302 $timeout.cancel(pendingDebounce);
25303
25304 // If the view value has not changed then we should just exit, except in the case where there is
25305 // a native validator on the element. In this case the validation state may have changed even though
25306 // the viewValue has stayed empty.
25307 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25308 return;
25309 }
25310 ctrl.$$lastCommittedViewValue = viewValue;
25311
25312 // change to dirty
25313 if (ctrl.$pristine) {
25314 this.$setDirty();
25315 }
25316 this.$$parseAndValidate();
25317 };
25318
25319 this.$$parseAndValidate = function() {
25320 var viewValue = ctrl.$$lastCommittedViewValue;
25321 var modelValue = viewValue;
25322 parserValid = isUndefined(modelValue) ? undefined : true;
25323
25324 if (parserValid) {
25325 for (var i = 0; i < ctrl.$parsers.length; i++) {
25326 modelValue = ctrl.$parsers[i](modelValue);
25327 if (isUndefined(modelValue)) {
25328 parserValid = false;
25329 break;
25330 }
25331 }
25332 }
25333 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25334 // ctrl.$modelValue has not been touched yet...
25335 ctrl.$modelValue = ngModelGet($scope);
25336 }
25337 var prevModelValue = ctrl.$modelValue;
25338 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25339 ctrl.$$rawModelValue = modelValue;
25340
25341 if (allowInvalid) {
25342 ctrl.$modelValue = modelValue;
25343 writeToModelIfNeeded();
25344 }
25345
25346 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25347 // This can happen if e.g. $setViewValue is called from inside a parser
25348 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25349 if (!allowInvalid) {
25350 // Note: Don't check ctrl.$valid here, as we could have
25351 // external validators (e.g. calculated on the server),
25352 // that just call $setValidity and need the model value
25353 // to calculate their validity.
25354 ctrl.$modelValue = allValid ? modelValue : undefined;
25355 writeToModelIfNeeded();
25356 }
25357 });
25358
25359 function writeToModelIfNeeded() {
25360 if (ctrl.$modelValue !== prevModelValue) {
25361 ctrl.$$writeModelToScope();
25362 }
25363 }
25364 };
25365
25366 this.$$writeModelToScope = function() {
25367 ngModelSet($scope, ctrl.$modelValue);
25368 forEach(ctrl.$viewChangeListeners, function(listener) {
25369 try {
25370 listener();
25371 } catch (e) {
25372 $exceptionHandler(e);
25373 }
25374 });
25375 };
25376
25377 /**
25378 * @ngdoc method
25379 * @name ngModel.NgModelController#$setViewValue
25380 *
25381 * @description
25382 * Update the view value.
25383 *
25384 * This method should be called when a control wants to change the view value; typically,
25385 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25386 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25387 * calls it when an option is selected.
25388 *
25389 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25390 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25391 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25392 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25393 * in the `$viewChangeListeners` list, are called.
25394 *
25395 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25396 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25397 * `updateOn` events is triggered on the DOM element.
25398 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25399 * directive is used with a custom debounce for this particular event.
25400 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25401 * is specified, once the timer runs out.
25402 *
25403 * When used with standard inputs, the view value will always be a string (which is in some cases
25404 * parsed into another type, such as a `Date` object for `input[date]`.)
25405 * However, custom controls might also pass objects to this method. In this case, we should make
25406 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25407 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25408 * the property of the object then ngModel will not realise that the object has changed and
25409 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25410 * not change properties of the copy once it has been passed to `$setViewValue`.
25411 * Otherwise you may cause the model value on the scope to change incorrectly.
25412 *
25413 * <div class="alert alert-info">
25414 * In any case, the value passed to the method should always reflect the current value
25415 * of the control. For example, if you are calling `$setViewValue` for an input element,
25416 * you should pass the input DOM value. Otherwise, the control and the scope model become
25417 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25418 * the control's DOM value in any way. If we want to change the control's DOM value
25419 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25420 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25421 * to update the DOM, and finally call `$validate` on it.
25422 * </div>
25423 *
25424 * @param {*} value value from the view.
25425 * @param {string} trigger Event that triggered the update.
25426 */
25427 this.$setViewValue = function(value, trigger) {
25428 ctrl.$viewValue = value;
25429 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25430 ctrl.$$debounceViewValueCommit(trigger);
25431 }
25432 };
25433
25434 this.$$debounceViewValueCommit = function(trigger) {
25435 var debounceDelay = 0,
25436 options = ctrl.$options,
25437 debounce;
25438
25439 if (options && isDefined(options.debounce)) {
25440 debounce = options.debounce;
25441 if (isNumber(debounce)) {
25442 debounceDelay = debounce;
25443 } else if (isNumber(debounce[trigger])) {
25444 debounceDelay = debounce[trigger];
25445 } else if (isNumber(debounce['default'])) {
25446 debounceDelay = debounce['default'];
25447 }
25448 }
25449
25450 $timeout.cancel(pendingDebounce);
25451 if (debounceDelay) {
25452 pendingDebounce = $timeout(function() {
25453 ctrl.$commitViewValue();
25454 }, debounceDelay);
25455 } else if ($rootScope.$$phase) {
25456 ctrl.$commitViewValue();
25457 } else {
25458 $scope.$apply(function() {
25459 ctrl.$commitViewValue();
25460 });
25461 }
25462 };
25463
25464 // model -> value
25465 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25466 // 1. scope value is 'a'
25467 // 2. user enters 'b'
25468 // 3. ng-change kicks in and reverts scope value to 'a'
25469 // -> scope value did not change since the last digest as
25470 // ng-change executes in apply phase
25471 // 4. view should be changed back to 'a'
25472 $scope.$watch(function ngModelWatch() {
25473 var modelValue = ngModelGet($scope);
25474
25475 // if scope model value and ngModel value are out of sync
25476 // TODO(perf): why not move this to the action fn?
25477 if (modelValue !== ctrl.$modelValue &&
25478 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25479 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25480 ) {
25481 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25482 parserValid = undefined;
25483
25484 var formatters = ctrl.$formatters,
25485 idx = formatters.length;
25486
25487 var viewValue = modelValue;
25488 while (idx--) {
25489 viewValue = formatters[idx](viewValue);
25490 }
25491 if (ctrl.$viewValue !== viewValue) {
25492 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25493 ctrl.$render();
25494
25495 ctrl.$$runValidators(modelValue, viewValue, noop);
25496 }
25497 }
25498
25499 return modelValue;
25500 });
25501}];
25502
25503
25504/**
25505 * @ngdoc directive
25506 * @name ngModel
25507 *
25508 * @element input
25509 * @priority 1
25510 *
25511 * @description
25512 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25513 * property on the scope using {@link ngModel.NgModelController NgModelController},
25514 * which is created and exposed by this directive.
25515 *
25516 * `ngModel` is responsible for:
25517 *
25518 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25519 * require.
25520 * - Providing validation behavior (i.e. required, number, email, url).
25521 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25522 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25523 * - Registering the control with its parent {@link ng.directive:form form}.
25524 *
25525 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25526 * current scope. If the property doesn't already exist on this scope, it will be created
25527 * implicitly and added to the scope.
25528 *
25529 * For best practices on using `ngModel`, see:
25530 *
25531 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25532 *
25533 * For basic examples, how to use `ngModel`, see:
25534 *
25535 * - {@link ng.directive:input input}
25536 * - {@link input[text] text}
25537 * - {@link input[checkbox] checkbox}
25538 * - {@link input[radio] radio}
25539 * - {@link input[number] number}
25540 * - {@link input[email] email}
25541 * - {@link input[url] url}
25542 * - {@link input[date] date}
25543 * - {@link input[datetime-local] datetime-local}
25544 * - {@link input[time] time}
25545 * - {@link input[month] month}
25546 * - {@link input[week] week}
25547 * - {@link ng.directive:select select}
25548 * - {@link ng.directive:textarea textarea}
25549 *
25550 * # CSS classes
25551 * The following CSS classes are added and removed on the associated input/select/textarea element
25552 * depending on the validity of the model.
25553 *
25554 * - `ng-valid`: the model is valid
25555 * - `ng-invalid`: the model is invalid
25556 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25557 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25558 * - `ng-pristine`: the control hasn't been interacted with yet
25559 * - `ng-dirty`: the control has been interacted with
25560 * - `ng-touched`: the control has been blurred
25561 * - `ng-untouched`: the control hasn't been blurred
25562 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25563 *
25564 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25565 *
25566 * ## Animation Hooks
25567 *
25568 * Animations within models are triggered when any of the associated CSS classes are added and removed
25569 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25570 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25571 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25572 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25573 *
25574 * The following example shows a simple way to utilize CSS transitions to style an input element
25575 * that has been rendered as invalid after it has been validated:
25576 *
25577 * <pre>
25578 * //be sure to include ngAnimate as a module to hook into more
25579 * //advanced animations
25580 * .my-input {
25581 * transition:0.5s linear all;
25582 * background: white;
25583 * }
25584 * .my-input.ng-invalid {
25585 * background: red;
25586 * color:white;
25587 * }
25588 * </pre>
25589 *
25590 * @example
25591 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25592 <file name="index.html">
25593 <script>
25594 angular.module('inputExample', [])
25595 .controller('ExampleController', ['$scope', function($scope) {
25596 $scope.val = '1';
25597 }]);
25598 </script>
25599 <style>
25600 .my-input {
25601 transition:all linear 0.5s;
25602 background: transparent;
25603 }
25604 .my-input.ng-invalid {
25605 color:white;
25606 background: red;
25607 }
25608 </style>
25609 <p id="inputDescription">
25610 Update input to see transitions when valid/invalid.
25611 Integer is a valid value.
25612 </p>
25613 <form name="testForm" ng-controller="ExampleController">
25614 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25615 aria-describedby="inputDescription" />
25616 </form>
25617 </file>
25618 * </example>
25619 *
25620 * ## Binding to a getter/setter
25621 *
25622 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25623 * function that returns a representation of the model when called with zero arguments, and sets
25624 * the internal state of a model when called with an argument. It's sometimes useful to use this
25625 * for models that have an internal representation that's different from what the model exposes
25626 * to the view.
25627 *
25628 * <div class="alert alert-success">
25629 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25630 * frequently than other parts of your code.
25631 * </div>
25632 *
25633 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25634 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25635 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25636 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25637 *
25638 * The following example shows how to use `ngModel` with a getter/setter:
25639 *
25640 * @example
25641 * <example name="ngModel-getter-setter" module="getterSetterExample">
25642 <file name="index.html">
25643 <div ng-controller="ExampleController">
25644 <form name="userForm">
25645 <label>Name:
25646 <input type="text" name="userName"
25647 ng-model="user.name"
25648 ng-model-options="{ getterSetter: true }" />
25649 </label>
25650 </form>
25651 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25652 </div>
25653 </file>
25654 <file name="app.js">
25655 angular.module('getterSetterExample', [])
25656 .controller('ExampleController', ['$scope', function($scope) {
25657 var _name = 'Brian';
25658 $scope.user = {
25659 name: function(newName) {
25660 // Note that newName can be undefined for two reasons:
25661 // 1. Because it is called as a getter and thus called with no arguments
25662 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25663 // input is invalid
25664 return arguments.length ? (_name = newName) : _name;
25665 }
25666 };
25667 }]);
25668 </file>
25669 * </example>
25670 */
25671var ngModelDirective = ['$rootScope', function($rootScope) {
25672 return {
25673 restrict: 'A',
25674 require: ['ngModel', '^?form', '^?ngModelOptions'],
25675 controller: NgModelController,
25676 // Prelink needs to run before any input directive
25677 // so that we can set the NgModelOptions in NgModelController
25678 // before anyone else uses it.
25679 priority: 1,
25680 compile: function ngModelCompile(element) {
25681 // Setup initial state of the control
25682 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25683
25684 return {
25685 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25686 var modelCtrl = ctrls[0],
25687 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25688
25689 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25690
25691 // notify others, especially parent forms
25692 formCtrl.$addControl(modelCtrl);
25693
25694 attr.$observe('name', function(newValue) {
25695 if (modelCtrl.$name !== newValue) {
25696 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25697 }
25698 });
25699
25700 scope.$on('$destroy', function() {
25701 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25702 });
25703 },
25704 post: function ngModelPostLink(scope, element, attr, ctrls) {
25705 var modelCtrl = ctrls[0];
25706 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25707 element.on(modelCtrl.$options.updateOn, function(ev) {
25708 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25709 });
25710 }
25711
25712 element.on('blur', function(ev) {
25713 if (modelCtrl.$touched) return;
25714
25715 if ($rootScope.$$phase) {
25716 scope.$evalAsync(modelCtrl.$setTouched);
25717 } else {
25718 scope.$apply(modelCtrl.$setTouched);
25719 }
25720 });
25721 }
25722 };
25723 }
25724 };
25725}];
25726
25727var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25728
25729/**
25730 * @ngdoc directive
25731 * @name ngModelOptions
25732 *
25733 * @description
25734 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25735 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25736 * takes place when a timer expires; this timer will be reset after another change takes place.
25737 *
25738 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25739 * be different from the value in the actual model. This means that if you update the model you
25740 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25741 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25742 *
25743 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25744 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25745 * important because `form` controllers are published to the related scope under the name in their
25746 * `name` attribute.
25747 *
25748 * Any pending changes will take place immediately when an enclosing form is submitted via the
25749 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25750 * to have access to the updated model.
25751 *
25752 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25753 *
25754 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25755 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25756 * events using an space delimited list. There is a special event called `default` that
25757 * matches the default events belonging of the control.
25758 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25759 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25760 * custom value for each event. For example:
25761 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25762 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25763 * not validate correctly instead of the default behavior of setting the model to undefined.
25764 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25765 `ngModel` as getters/setters.
25766 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25767 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25768 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25769 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25770 * If not specified, the timezone of the browser will be used.
25771 *
25772 * @example
25773
25774 The following example shows how to override immediate updates. Changes on the inputs within the
25775 form will update the model only when the control loses focus (blur event). If `escape` key is
25776 pressed while the input field is focused, the value is reset to the value in the current model.
25777
25778 <example name="ngModelOptions-directive-blur" module="optionsExample">
25779 <file name="index.html">
25780 <div ng-controller="ExampleController">
25781 <form name="userForm">
25782 <label>Name:
25783 <input type="text" name="userName"
25784 ng-model="user.name"
25785 ng-model-options="{ updateOn: 'blur' }"
25786 ng-keyup="cancel($event)" />
25787 </label><br />
25788 <label>Other data:
25789 <input type="text" ng-model="user.data" />
25790 </label><br />
25791 </form>
25792 <pre>user.name = <span ng-bind="user.name"></span></pre>
25793 <pre>user.data = <span ng-bind="user.data"></span></pre>
25794 </div>
25795 </file>
25796 <file name="app.js">
25797 angular.module('optionsExample', [])
25798 .controller('ExampleController', ['$scope', function($scope) {
25799 $scope.user = { name: 'John', data: '' };
25800
25801 $scope.cancel = function(e) {
25802 if (e.keyCode == 27) {
25803 $scope.userForm.userName.$rollbackViewValue();
25804 }
25805 };
25806 }]);
25807 </file>
25808 <file name="protractor.js" type="protractor">
25809 var model = element(by.binding('user.name'));
25810 var input = element(by.model('user.name'));
25811 var other = element(by.model('user.data'));
25812
25813 it('should allow custom events', function() {
25814 input.sendKeys(' Doe');
25815 input.click();
25816 expect(model.getText()).toEqual('John');
25817 other.click();
25818 expect(model.getText()).toEqual('John Doe');
25819 });
25820
25821 it('should $rollbackViewValue when model changes', function() {
25822 input.sendKeys(' Doe');
25823 expect(input.getAttribute('value')).toEqual('John Doe');
25824 input.sendKeys(protractor.Key.ESCAPE);
25825 expect(input.getAttribute('value')).toEqual('John');
25826 other.click();
25827 expect(model.getText()).toEqual('John');
25828 });
25829 </file>
25830 </example>
25831
25832 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25833 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25834
25835 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25836 <file name="index.html">
25837 <div ng-controller="ExampleController">
25838 <form name="userForm">
25839 <label>Name:
25840 <input type="text" name="userName"
25841 ng-model="user.name"
25842 ng-model-options="{ debounce: 1000 }" />
25843 </label>
25844 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25845 <br />
25846 </form>
25847 <pre>user.name = <span ng-bind="user.name"></span></pre>
25848 </div>
25849 </file>
25850 <file name="app.js">
25851 angular.module('optionsExample', [])
25852 .controller('ExampleController', ['$scope', function($scope) {
25853 $scope.user = { name: 'Igor' };
25854 }]);
25855 </file>
25856 </example>
25857
25858 This one shows how to bind to getter/setters:
25859
25860 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25861 <file name="index.html">
25862 <div ng-controller="ExampleController">
25863 <form name="userForm">
25864 <label>Name:
25865 <input type="text" name="userName"
25866 ng-model="user.name"
25867 ng-model-options="{ getterSetter: true }" />
25868 </label>
25869 </form>
25870 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25871 </div>
25872 </file>
25873 <file name="app.js">
25874 angular.module('getterSetterExample', [])
25875 .controller('ExampleController', ['$scope', function($scope) {
25876 var _name = 'Brian';
25877 $scope.user = {
25878 name: function(newName) {
25879 // Note that newName can be undefined for two reasons:
25880 // 1. Because it is called as a getter and thus called with no arguments
25881 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25882 // input is invalid
25883 return arguments.length ? (_name = newName) : _name;
25884 }
25885 };
25886 }]);
25887 </file>
25888 </example>
25889 */
25890var ngModelOptionsDirective = function() {
25891 return {
25892 restrict: 'A',
25893 controller: ['$scope', '$attrs', function($scope, $attrs) {
25894 var that = this;
25895 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25896 // Allow adding/overriding bound events
25897 if (isDefined(this.$options.updateOn)) {
25898 this.$options.updateOnDefault = false;
25899 // extract "default" pseudo-event from list of events that can trigger a model update
25900 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25901 that.$options.updateOnDefault = true;
25902 return ' ';
25903 }));
25904 } else {
25905 this.$options.updateOnDefault = true;
25906 }
25907 }]
25908 };
25909};
25910
25911
25912
25913// helper methods
25914function addSetValidityMethod(context) {
25915 var ctrl = context.ctrl,
25916 $element = context.$element,
25917 classCache = {},
25918 set = context.set,
25919 unset = context.unset,
25920 $animate = context.$animate;
25921
25922 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
25923
25924 ctrl.$setValidity = setValidity;
25925
25926 function setValidity(validationErrorKey, state, controller) {
25927 if (isUndefined(state)) {
25928 createAndSet('$pending', validationErrorKey, controller);
25929 } else {
25930 unsetAndCleanup('$pending', validationErrorKey, controller);
25931 }
25932 if (!isBoolean(state)) {
25933 unset(ctrl.$error, validationErrorKey, controller);
25934 unset(ctrl.$$success, validationErrorKey, controller);
25935 } else {
25936 if (state) {
25937 unset(ctrl.$error, validationErrorKey, controller);
25938 set(ctrl.$$success, validationErrorKey, controller);
25939 } else {
25940 set(ctrl.$error, validationErrorKey, controller);
25941 unset(ctrl.$$success, validationErrorKey, controller);
25942 }
25943 }
25944 if (ctrl.$pending) {
25945 cachedToggleClass(PENDING_CLASS, true);
25946 ctrl.$valid = ctrl.$invalid = undefined;
25947 toggleValidationCss('', null);
25948 } else {
25949 cachedToggleClass(PENDING_CLASS, false);
25950 ctrl.$valid = isObjectEmpty(ctrl.$error);
25951 ctrl.$invalid = !ctrl.$valid;
25952 toggleValidationCss('', ctrl.$valid);
25953 }
25954
25955 // re-read the state as the set/unset methods could have
25956 // combined state in ctrl.$error[validationError] (used for forms),
25957 // where setting/unsetting only increments/decrements the value,
25958 // and does not replace it.
25959 var combinedState;
25960 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
25961 combinedState = undefined;
25962 } else if (ctrl.$error[validationErrorKey]) {
25963 combinedState = false;
25964 } else if (ctrl.$$success[validationErrorKey]) {
25965 combinedState = true;
25966 } else {
25967 combinedState = null;
25968 }
25969
25970 toggleValidationCss(validationErrorKey, combinedState);
25971 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
25972 }
25973
25974 function createAndSet(name, value, controller) {
25975 if (!ctrl[name]) {
25976 ctrl[name] = {};
25977 }
25978 set(ctrl[name], value, controller);
25979 }
25980
25981 function unsetAndCleanup(name, value, controller) {
25982 if (ctrl[name]) {
25983 unset(ctrl[name], value, controller);
25984 }
25985 if (isObjectEmpty(ctrl[name])) {
25986 ctrl[name] = undefined;
25987 }
25988 }
25989
25990 function cachedToggleClass(className, switchValue) {
25991 if (switchValue && !classCache[className]) {
25992 $animate.addClass($element, className);
25993 classCache[className] = true;
25994 } else if (!switchValue && classCache[className]) {
25995 $animate.removeClass($element, className);
25996 classCache[className] = false;
25997 }
25998 }
25999
26000 function toggleValidationCss(validationErrorKey, isValid) {
26001 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26002
26003 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26004 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26005 }
26006}
26007
26008function isObjectEmpty(obj) {
26009 if (obj) {
26010 for (var prop in obj) {
26011 if (obj.hasOwnProperty(prop)) {
26012 return false;
26013 }
26014 }
26015 }
26016 return true;
26017}
26018
26019/**
26020 * @ngdoc directive
26021 * @name ngNonBindable
26022 * @restrict AC
26023 * @priority 1000
26024 *
26025 * @description
26026 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26027 * DOM element. This is useful if the element contains what appears to be Angular directives and
26028 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26029 * displays snippets of code, for instance.
26030 *
26031 * @element ANY
26032 *
26033 * @example
26034 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26035 * but the one wrapped in `ngNonBindable` is left alone.
26036 *
26037 * @example
26038 <example>
26039 <file name="index.html">
26040 <div>Normal: {{1 + 2}}</div>
26041 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26042 </file>
26043 <file name="protractor.js" type="protractor">
26044 it('should check ng-non-bindable', function() {
26045 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26046 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26047 });
26048 </file>
26049 </example>
26050 */
26051var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26052
26053/* global jqLiteRemove */
26054
26055var ngOptionsMinErr = minErr('ngOptions');
26056
26057/**
26058 * @ngdoc directive
26059 * @name ngOptions
26060 * @restrict A
26061 *
26062 * @description
26063 *
26064 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26065 * elements for the `<select>` element using the array or object obtained by evaluating the
26066 * `ngOptions` comprehension expression.
26067 *
26068 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26069 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26070 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26071 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26072 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26073 * to a non-string value. This is because an option element can only be bound to string values at
26074 * present.
26075 *
26076 * When an item in the `<select>` menu is selected, the array element or object property
26077 * represented by the selected option will be bound to the model identified by the `ngModel`
26078 * directive.
26079 *
26080 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26081 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26082 * option. See example below for demonstration.
26083 *
26084 * ## Complex Models (objects or collections)
26085 *
26086 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26087 * binding the select to a model that is an object or a collection.
26088 *
26089 * One issue occurs if you want to preselect an option. For example, if you set
26090 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26091 * because the objects are not identical. So by default, you should always reference the item in your collection
26092 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26093 *
26094 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26095 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26096 * collection items have an id property, you would `track by item.id`.
26097 *
26098 * A different issue with objects or collections is that ngModel won't detect if an object property or
26099 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26100 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26101 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26102 * has not changed identity, but only a property on the object or an item in the collection changes.
26103 *
26104 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26105 * if the model is an array). This means that changing a property deeper than the first level inside the
26106 * object/collection will not trigger a re-rendering.
26107 *
26108 * ## `select` **`as`**
26109 *
26110 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26111 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26112 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26113 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26114 *
26115 *
26116 * ### `select` **`as`** and **`track by`**
26117 *
26118 * <div class="alert alert-warning">
26119 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26120 * </div>
26121 *
26122 * Given this array of items on the $scope:
26123 *
26124 * ```js
26125 * $scope.items = [{
26126 * id: 1,
26127 * label: 'aLabel',
26128 * subItem: { name: 'aSubItem' }
26129 * }, {
26130 * id: 2,
26131 * label: 'bLabel',
26132 * subItem: { name: 'bSubItem' }
26133 * }];
26134 * ```
26135 *
26136 * This will work:
26137 *
26138 * ```html
26139 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26140 * ```
26141 * ```js
26142 * $scope.selected = $scope.items[0];
26143 * ```
26144 *
26145 * but this will not work:
26146 *
26147 * ```html
26148 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26149 * ```
26150 * ```js
26151 * $scope.selected = $scope.items[0].subItem;
26152 * ```
26153 *
26154 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26155 * `items` array. Because the selected option has been set programmatically in the controller, the
26156 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26157 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26158 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26159 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26160 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26161 *
26162 *
26163 * @param {string} ngModel Assignable angular expression to data-bind to.
26164 * @param {string=} name Property name of the form under which the control is published.
26165 * @param {string=} required The control is considered valid only if value is entered.
26166 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26167 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26168 * `required` when you want to data-bind to the `required` attribute.
26169 * @param {comprehension_expression=} ngOptions in one of the following forms:
26170 *
26171 * * for array data sources:
26172 * * `label` **`for`** `value` **`in`** `array`
26173 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26174 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26175 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26176 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26177 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26178 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26179 * (for including a filter with `track by`)
26180 * * for object data sources:
26181 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26182 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26183 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26184 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26185 * * `select` **`as`** `label` **`group by`** `group`
26186 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26187 * * `select` **`as`** `label` **`disable when`** `disable`
26188 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26189 *
26190 * Where:
26191 *
26192 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26193 * * `value`: local variable which will refer to each item in the `array` or each property value
26194 * of `object` during iteration.
26195 * * `key`: local variable which will refer to a property name in `object` during iteration.
26196 * * `label`: The result of this expression will be the label for `<option>` element. The
26197 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26198 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26199 * element. If not specified, `select` expression will default to `value`.
26200 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26201 * DOM element.
26202 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26203 * element. Return `true` to disable.
26204 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26205 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26206 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26207 * even when the options are recreated (e.g. reloaded from the server).
26208 *
26209 * @example
26210 <example module="selectExample">
26211 <file name="index.html">
26212 <script>
26213 angular.module('selectExample', [])
26214 .controller('ExampleController', ['$scope', function($scope) {
26215 $scope.colors = [
26216 {name:'black', shade:'dark'},
26217 {name:'white', shade:'light', notAnOption: true},
26218 {name:'red', shade:'dark'},
26219 {name:'blue', shade:'dark', notAnOption: true},
26220 {name:'yellow', shade:'light', notAnOption: false}
26221 ];
26222 $scope.myColor = $scope.colors[2]; // red
26223 }]);
26224 </script>
26225 <div ng-controller="ExampleController">
26226 <ul>
26227 <li ng-repeat="color in colors">
26228 <label>Name: <input ng-model="color.name"></label>
26229 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26230 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26231 </li>
26232 <li>
26233 <button ng-click="colors.push({})">add</button>
26234 </li>
26235 </ul>
26236 <hr/>
26237 <label>Color (null not allowed):
26238 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26239 </label><br/>
26240 <label>Color (null allowed):
26241 <span class="nullable">
26242 <select ng-model="myColor" ng-options="color.name for color in colors">
26243 <option value="">-- choose color --</option>
26244 </select>
26245 </span></label><br/>
26246
26247 <label>Color grouped by shade:
26248 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26249 </select>
26250 </label><br/>
26251
26252 <label>Color grouped by shade, with some disabled:
26253 <select ng-model="myColor"
26254 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26255 </select>
26256 </label><br/>
26257
26258
26259
26260 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26261 <br/>
26262 <hr/>
26263 Currently selected: {{ {selected_color:myColor} }}
26264 <div style="border:solid 1px black; height:20px"
26265 ng-style="{'background-color':myColor.name}">
26266 </div>
26267 </div>
26268 </file>
26269 <file name="protractor.js" type="protractor">
26270 it('should check ng-options', function() {
26271 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26272 element.all(by.model('myColor')).first().click();
26273 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26274 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26275 element(by.css('.nullable select[ng-model="myColor"]')).click();
26276 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26277 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26278 });
26279 </file>
26280 </example>
26281 */
26282
26283// jshint maxlen: false
26284// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26285var 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]+?))?$/;
26286 // 1: value expression (valueFn)
26287 // 2: label expression (displayFn)
26288 // 3: group by expression (groupByFn)
26289 // 4: disable when expression (disableWhenFn)
26290 // 5: array item variable name
26291 // 6: object item key variable name
26292 // 7: object item value variable name
26293 // 8: collection expression
26294 // 9: track by expression
26295// jshint maxlen: 100
26296
26297
26298var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26299
26300 function parseOptionsExpression(optionsExp, selectElement, scope) {
26301
26302 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26303 if (!(match)) {
26304 throw ngOptionsMinErr('iexp',
26305 "Expected expression in form of " +
26306 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26307 " but got '{0}'. Element: {1}",
26308 optionsExp, startingTag(selectElement));
26309 }
26310
26311 // Extract the parts from the ngOptions expression
26312
26313 // The variable name for the value of the item in the collection
26314 var valueName = match[5] || match[7];
26315 // The variable name for the key of the item in the collection
26316 var keyName = match[6];
26317
26318 // An expression that generates the viewValue for an option if there is a label expression
26319 var selectAs = / as /.test(match[0]) && match[1];
26320 // An expression that is used to track the id of each object in the options collection
26321 var trackBy = match[9];
26322 // An expression that generates the viewValue for an option if there is no label expression
26323 var valueFn = $parse(match[2] ? match[1] : valueName);
26324 var selectAsFn = selectAs && $parse(selectAs);
26325 var viewValueFn = selectAsFn || valueFn;
26326 var trackByFn = trackBy && $parse(trackBy);
26327
26328 // Get the value by which we are going to track the option
26329 // if we have a trackFn then use that (passing scope and locals)
26330 // otherwise just hash the given viewValue
26331 var getTrackByValueFn = trackBy ?
26332 function(value, locals) { return trackByFn(scope, locals); } :
26333 function getHashOfValue(value) { return hashKey(value); };
26334 var getTrackByValue = function(value, key) {
26335 return getTrackByValueFn(value, getLocals(value, key));
26336 };
26337
26338 var displayFn = $parse(match[2] || match[1]);
26339 var groupByFn = $parse(match[3] || '');
26340 var disableWhenFn = $parse(match[4] || '');
26341 var valuesFn = $parse(match[8]);
26342
26343 var locals = {};
26344 var getLocals = keyName ? function(value, key) {
26345 locals[keyName] = key;
26346 locals[valueName] = value;
26347 return locals;
26348 } : function(value) {
26349 locals[valueName] = value;
26350 return locals;
26351 };
26352
26353
26354 function Option(selectValue, viewValue, label, group, disabled) {
26355 this.selectValue = selectValue;
26356 this.viewValue = viewValue;
26357 this.label = label;
26358 this.group = group;
26359 this.disabled = disabled;
26360 }
26361
26362 function getOptionValuesKeys(optionValues) {
26363 var optionValuesKeys;
26364
26365 if (!keyName && isArrayLike(optionValues)) {
26366 optionValuesKeys = optionValues;
26367 } else {
26368 // if object, extract keys, in enumeration order, unsorted
26369 optionValuesKeys = [];
26370 for (var itemKey in optionValues) {
26371 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26372 optionValuesKeys.push(itemKey);
26373 }
26374 }
26375 }
26376 return optionValuesKeys;
26377 }
26378
26379 return {
26380 trackBy: trackBy,
26381 getTrackByValue: getTrackByValue,
26382 getWatchables: $parse(valuesFn, function(optionValues) {
26383 // Create a collection of things that we would like to watch (watchedArray)
26384 // so that they can all be watched using a single $watchCollection
26385 // that only runs the handler once if anything changes
26386 var watchedArray = [];
26387 optionValues = optionValues || [];
26388
26389 var optionValuesKeys = getOptionValuesKeys(optionValues);
26390 var optionValuesLength = optionValuesKeys.length;
26391 for (var index = 0; index < optionValuesLength; index++) {
26392 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26393 var value = optionValues[key];
26394
26395 var locals = getLocals(optionValues[key], key);
26396 var selectValue = getTrackByValueFn(optionValues[key], locals);
26397 watchedArray.push(selectValue);
26398
26399 // Only need to watch the displayFn if there is a specific label expression
26400 if (match[2] || match[1]) {
26401 var label = displayFn(scope, locals);
26402 watchedArray.push(label);
26403 }
26404
26405 // Only need to watch the disableWhenFn if there is a specific disable expression
26406 if (match[4]) {
26407 var disableWhen = disableWhenFn(scope, locals);
26408 watchedArray.push(disableWhen);
26409 }
26410 }
26411 return watchedArray;
26412 }),
26413
26414 getOptions: function() {
26415
26416 var optionItems = [];
26417 var selectValueMap = {};
26418
26419 // The option values were already computed in the `getWatchables` fn,
26420 // which must have been called to trigger `getOptions`
26421 var optionValues = valuesFn(scope) || [];
26422 var optionValuesKeys = getOptionValuesKeys(optionValues);
26423 var optionValuesLength = optionValuesKeys.length;
26424
26425 for (var index = 0; index < optionValuesLength; index++) {
26426 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26427 var value = optionValues[key];
26428 var locals = getLocals(value, key);
26429 var viewValue = viewValueFn(scope, locals);
26430 var selectValue = getTrackByValueFn(viewValue, locals);
26431 var label = displayFn(scope, locals);
26432 var group = groupByFn(scope, locals);
26433 var disabled = disableWhenFn(scope, locals);
26434 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26435
26436 optionItems.push(optionItem);
26437 selectValueMap[selectValue] = optionItem;
26438 }
26439
26440 return {
26441 items: optionItems,
26442 selectValueMap: selectValueMap,
26443 getOptionFromViewValue: function(value) {
26444 return selectValueMap[getTrackByValue(value)];
26445 },
26446 getViewValueFromOption: function(option) {
26447 // If the viewValue could be an object that may be mutated by the application,
26448 // we need to make a copy and not return the reference to the value on the option.
26449 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26450 }
26451 };
26452 }
26453 };
26454 }
26455
26456
26457 // we can't just jqLite('<option>') since jqLite is not smart enough
26458 // to create it in <select> and IE barfs otherwise.
26459 var optionTemplate = document.createElement('option'),
26460 optGroupTemplate = document.createElement('optgroup');
26461
26462
26463 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26464
26465 // if ngModel is not defined, we don't need to do anything
26466 var ngModelCtrl = ctrls[1];
26467 if (!ngModelCtrl) return;
26468
26469 var selectCtrl = ctrls[0];
26470 var multiple = attr.multiple;
26471
26472 // The emptyOption allows the application developer to provide their own custom "empty"
26473 // option when the viewValue does not match any of the option values.
26474 var emptyOption;
26475 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26476 if (children[i].value === '') {
26477 emptyOption = children.eq(i);
26478 break;
26479 }
26480 }
26481
26482 var providedEmptyOption = !!emptyOption;
26483
26484 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26485 unknownOption.val('?');
26486
26487 var options;
26488 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26489
26490
26491 var renderEmptyOption = function() {
26492 if (!providedEmptyOption) {
26493 selectElement.prepend(emptyOption);
26494 }
26495 selectElement.val('');
26496 emptyOption.prop('selected', true); // needed for IE
26497 emptyOption.attr('selected', true);
26498 };
26499
26500 var removeEmptyOption = function() {
26501 if (!providedEmptyOption) {
26502 emptyOption.remove();
26503 }
26504 };
26505
26506
26507 var renderUnknownOption = function() {
26508 selectElement.prepend(unknownOption);
26509 selectElement.val('?');
26510 unknownOption.prop('selected', true); // needed for IE
26511 unknownOption.attr('selected', true);
26512 };
26513
26514 var removeUnknownOption = function() {
26515 unknownOption.remove();
26516 };
26517
26518 // Update the controller methods for multiple selectable options
26519 if (!multiple) {
26520
26521 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26522 var option = options.getOptionFromViewValue(value);
26523
26524 if (option && !option.disabled) {
26525 if (selectElement[0].value !== option.selectValue) {
26526 removeUnknownOption();
26527 removeEmptyOption();
26528
26529 selectElement[0].value = option.selectValue;
26530 option.element.selected = true;
26531 option.element.setAttribute('selected', 'selected');
26532 }
26533 } else {
26534 if (value === null || providedEmptyOption) {
26535 removeUnknownOption();
26536 renderEmptyOption();
26537 } else {
26538 removeEmptyOption();
26539 renderUnknownOption();
26540 }
26541 }
26542 };
26543
26544 selectCtrl.readValue = function readNgOptionsValue() {
26545
26546 var selectedOption = options.selectValueMap[selectElement.val()];
26547
26548 if (selectedOption && !selectedOption.disabled) {
26549 removeEmptyOption();
26550 removeUnknownOption();
26551 return options.getViewValueFromOption(selectedOption);
26552 }
26553 return null;
26554 };
26555
26556 // If we are using `track by` then we must watch the tracked value on the model
26557 // since ngModel only watches for object identity change
26558 if (ngOptions.trackBy) {
26559 scope.$watch(
26560 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26561 function() { ngModelCtrl.$render(); }
26562 );
26563 }
26564
26565 } else {
26566
26567 ngModelCtrl.$isEmpty = function(value) {
26568 return !value || value.length === 0;
26569 };
26570
26571
26572 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26573 options.items.forEach(function(option) {
26574 option.element.selected = false;
26575 });
26576
26577 if (value) {
26578 value.forEach(function(item) {
26579 var option = options.getOptionFromViewValue(item);
26580 if (option && !option.disabled) option.element.selected = true;
26581 });
26582 }
26583 };
26584
26585
26586 selectCtrl.readValue = function readNgOptionsMultiple() {
26587 var selectedValues = selectElement.val() || [],
26588 selections = [];
26589
26590 forEach(selectedValues, function(value) {
26591 var option = options.selectValueMap[value];
26592 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26593 });
26594
26595 return selections;
26596 };
26597
26598 // If we are using `track by` then we must watch these tracked values on the model
26599 // since ngModel only watches for object identity change
26600 if (ngOptions.trackBy) {
26601
26602 scope.$watchCollection(function() {
26603 if (isArray(ngModelCtrl.$viewValue)) {
26604 return ngModelCtrl.$viewValue.map(function(value) {
26605 return ngOptions.getTrackByValue(value);
26606 });
26607 }
26608 }, function() {
26609 ngModelCtrl.$render();
26610 });
26611
26612 }
26613 }
26614
26615
26616 if (providedEmptyOption) {
26617
26618 // we need to remove it before calling selectElement.empty() because otherwise IE will
26619 // remove the label from the element. wtf?
26620 emptyOption.remove();
26621
26622 // compile the element since there might be bindings in it
26623 $compile(emptyOption)(scope);
26624
26625 // remove the class, which is added automatically because we recompile the element and it
26626 // becomes the compilation root
26627 emptyOption.removeClass('ng-scope');
26628 } else {
26629 emptyOption = jqLite(optionTemplate.cloneNode(false));
26630 }
26631
26632 // We need to do this here to ensure that the options object is defined
26633 // when we first hit it in writeNgOptionsValue
26634 updateOptions();
26635
26636 // We will re-render the option elements if the option values or labels change
26637 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26638
26639 // ------------------------------------------------------------------ //
26640
26641
26642 function updateOptionElement(option, element) {
26643 option.element = element;
26644 element.disabled = option.disabled;
26645 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26646 // selects in certain circumstances when multiple selects are next to each other and display
26647 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26648 // See https://github.com/angular/angular.js/issues/11314 for more info.
26649 // This is unfortunately untestable with unit / e2e tests
26650 if (option.label !== element.label) {
26651 element.label = option.label;
26652 element.textContent = option.label;
26653 }
26654 if (option.value !== element.value) element.value = option.selectValue;
26655 }
26656
26657 function addOrReuseElement(parent, current, type, templateElement) {
26658 var element;
26659 // Check whether we can reuse the next element
26660 if (current && lowercase(current.nodeName) === type) {
26661 // The next element is the right type so reuse it
26662 element = current;
26663 } else {
26664 // The next element is not the right type so create a new one
26665 element = templateElement.cloneNode(false);
26666 if (!current) {
26667 // There are no more elements so just append it to the select
26668 parent.appendChild(element);
26669 } else {
26670 // The next element is not a group so insert the new one
26671 parent.insertBefore(element, current);
26672 }
26673 }
26674 return element;
26675 }
26676
26677
26678 function removeExcessElements(current) {
26679 var next;
26680 while (current) {
26681 next = current.nextSibling;
26682 jqLiteRemove(current);
26683 current = next;
26684 }
26685 }
26686
26687
26688 function skipEmptyAndUnknownOptions(current) {
26689 var emptyOption_ = emptyOption && emptyOption[0];
26690 var unknownOption_ = unknownOption && unknownOption[0];
26691
26692 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26693 // because the compiled empty option might have been replaced by a comment because
26694 // it had an "element" transclusion directive on it (such as ngIf)
26695 if (emptyOption_ || unknownOption_) {
26696 while (current &&
26697 (current === emptyOption_ ||
26698 current === unknownOption_ ||
26699 current.nodeType === NODE_TYPE_COMMENT ||
26700 current.value === '')) {
26701 current = current.nextSibling;
26702 }
26703 }
26704 return current;
26705 }
26706
26707
26708 function updateOptions() {
26709
26710 var previousValue = options && selectCtrl.readValue();
26711
26712 options = ngOptions.getOptions();
26713
26714 var groupMap = {};
26715 var currentElement = selectElement[0].firstChild;
26716
26717 // Ensure that the empty option is always there if it was explicitly provided
26718 if (providedEmptyOption) {
26719 selectElement.prepend(emptyOption);
26720 }
26721
26722 currentElement = skipEmptyAndUnknownOptions(currentElement);
26723
26724 options.items.forEach(function updateOption(option) {
26725 var group;
26726 var groupElement;
26727 var optionElement;
26728
26729 if (option.group) {
26730
26731 // This option is to live in a group
26732 // See if we have already created this group
26733 group = groupMap[option.group];
26734
26735 if (!group) {
26736
26737 // We have not already created this group
26738 groupElement = addOrReuseElement(selectElement[0],
26739 currentElement,
26740 'optgroup',
26741 optGroupTemplate);
26742 // Move to the next element
26743 currentElement = groupElement.nextSibling;
26744
26745 // Update the label on the group element
26746 groupElement.label = option.group;
26747
26748 // Store it for use later
26749 group = groupMap[option.group] = {
26750 groupElement: groupElement,
26751 currentOptionElement: groupElement.firstChild
26752 };
26753
26754 }
26755
26756 // So now we have a group for this option we add the option to the group
26757 optionElement = addOrReuseElement(group.groupElement,
26758 group.currentOptionElement,
26759 'option',
26760 optionTemplate);
26761 updateOptionElement(option, optionElement);
26762 // Move to the next element
26763 group.currentOptionElement = optionElement.nextSibling;
26764
26765 } else {
26766
26767 // This option is not in a group
26768 optionElement = addOrReuseElement(selectElement[0],
26769 currentElement,
26770 'option',
26771 optionTemplate);
26772 updateOptionElement(option, optionElement);
26773 // Move to the next element
26774 currentElement = optionElement.nextSibling;
26775 }
26776 });
26777
26778
26779 // Now remove all excess options and group
26780 Object.keys(groupMap).forEach(function(key) {
26781 removeExcessElements(groupMap[key].currentOptionElement);
26782 });
26783 removeExcessElements(currentElement);
26784
26785 ngModelCtrl.$render();
26786
26787 // Check to see if the value has changed due to the update to the options
26788 if (!ngModelCtrl.$isEmpty(previousValue)) {
26789 var nextValue = selectCtrl.readValue();
26790 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26791 ngModelCtrl.$setViewValue(nextValue);
26792 ngModelCtrl.$render();
26793 }
26794 }
26795
26796 }
26797 }
26798
26799 return {
26800 restrict: 'A',
26801 terminal: true,
26802 require: ['select', '?ngModel'],
26803 link: {
26804 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26805 // Deactivate the SelectController.register method to prevent
26806 // option directives from accidentally registering themselves
26807 // (and unwanted $destroy handlers etc.)
26808 ctrls[0].registerOption = noop;
26809 },
26810 post: ngOptionsPostLink
26811 }
26812 };
26813}];
26814
26815/**
26816 * @ngdoc directive
26817 * @name ngPluralize
26818 * @restrict EA
26819 *
26820 * @description
26821 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26822 * These rules are bundled with angular.js, but can be overridden
26823 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26824 * by specifying the mappings between
26825 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26826 * and the strings to be displayed.
26827 *
26828 * # Plural categories and explicit number rules
26829 * There are two
26830 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26831 * in Angular's default en-US locale: "one" and "other".
26832 *
26833 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26834 * any number that is not 1), an explicit number rule can only match one number. For example, the
26835 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26836 * and explicit number rules throughout the rest of this documentation.
26837 *
26838 * # Configuring ngPluralize
26839 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26840 * You can also provide an optional attribute, `offset`.
26841 *
26842 * The value of the `count` attribute can be either a string or an {@link guide/expression
26843 * Angular expression}; these are evaluated on the current scope for its bound value.
26844 *
26845 * The `when` attribute specifies the mappings between plural categories and the actual
26846 * string to be displayed. The value of the attribute should be a JSON object.
26847 *
26848 * The following example shows how to configure ngPluralize:
26849 *
26850 * ```html
26851 * <ng-pluralize count="personCount"
26852 when="{'0': 'Nobody is viewing.',
26853 * 'one': '1 person is viewing.',
26854 * 'other': '{} people are viewing.'}">
26855 * </ng-pluralize>
26856 *```
26857 *
26858 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26859 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26860 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26861 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26862 * show "a dozen people are viewing".
26863 *
26864 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26865 * into pluralized strings. In the previous example, Angular will replace `{}` with
26866 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26867 * for <span ng-non-bindable>{{numberExpression}}</span>.
26868 *
26869 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26870 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26871 *
26872 * # Configuring ngPluralize with offset
26873 * The `offset` attribute allows further customization of pluralized text, which can result in
26874 * a better user experience. For example, instead of the message "4 people are viewing this document",
26875 * you might display "John, Kate and 2 others are viewing this document".
26876 * The offset attribute allows you to offset a number by any desired value.
26877 * Let's take a look at an example:
26878 *
26879 * ```html
26880 * <ng-pluralize count="personCount" offset=2
26881 * when="{'0': 'Nobody is viewing.',
26882 * '1': '{{person1}} is viewing.',
26883 * '2': '{{person1}} and {{person2}} are viewing.',
26884 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26885 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26886 * </ng-pluralize>
26887 * ```
26888 *
26889 * Notice that we are still using two plural categories(one, other), but we added
26890 * three explicit number rules 0, 1 and 2.
26891 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26892 * When three people view the document, no explicit number rule is found, so
26893 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26894 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26895 * is shown.
26896 *
26897 * Note that when you specify offsets, you must provide explicit number rules for
26898 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26899 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26900 * plural categories "one" and "other".
26901 *
26902 * @param {string|expression} count The variable to be bound to.
26903 * @param {string} when The mapping between plural category to its corresponding strings.
26904 * @param {number=} offset Offset to deduct from the total number.
26905 *
26906 * @example
26907 <example module="pluralizeExample">
26908 <file name="index.html">
26909 <script>
26910 angular.module('pluralizeExample', [])
26911 .controller('ExampleController', ['$scope', function($scope) {
26912 $scope.person1 = 'Igor';
26913 $scope.person2 = 'Misko';
26914 $scope.personCount = 1;
26915 }]);
26916 </script>
26917 <div ng-controller="ExampleController">
26918 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26919 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26920 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
26921
26922 <!--- Example with simple pluralization rules for en locale --->
26923 Without Offset:
26924 <ng-pluralize count="personCount"
26925 when="{'0': 'Nobody is viewing.',
26926 'one': '1 person is viewing.',
26927 'other': '{} people are viewing.'}">
26928 </ng-pluralize><br>
26929
26930 <!--- Example with offset --->
26931 With Offset(2):
26932 <ng-pluralize count="personCount" offset=2
26933 when="{'0': 'Nobody is viewing.',
26934 '1': '{{person1}} is viewing.',
26935 '2': '{{person1}} and {{person2}} are viewing.',
26936 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26937 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26938 </ng-pluralize>
26939 </div>
26940 </file>
26941 <file name="protractor.js" type="protractor">
26942 it('should show correct pluralized string', function() {
26943 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
26944 var withOffset = element.all(by.css('ng-pluralize')).get(1);
26945 var countInput = element(by.model('personCount'));
26946
26947 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
26948 expect(withOffset.getText()).toEqual('Igor is viewing.');
26949
26950 countInput.clear();
26951 countInput.sendKeys('0');
26952
26953 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
26954 expect(withOffset.getText()).toEqual('Nobody is viewing.');
26955
26956 countInput.clear();
26957 countInput.sendKeys('2');
26958
26959 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
26960 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
26961
26962 countInput.clear();
26963 countInput.sendKeys('3');
26964
26965 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
26966 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
26967
26968 countInput.clear();
26969 countInput.sendKeys('4');
26970
26971 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
26972 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
26973 });
26974 it('should show data-bound names', function() {
26975 var withOffset = element.all(by.css('ng-pluralize')).get(1);
26976 var personCount = element(by.model('personCount'));
26977 var person1 = element(by.model('person1'));
26978 var person2 = element(by.model('person2'));
26979 personCount.clear();
26980 personCount.sendKeys('4');
26981 person1.clear();
26982 person1.sendKeys('Di');
26983 person2.clear();
26984 person2.sendKeys('Vojta');
26985 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
26986 });
26987 </file>
26988 </example>
26989 */
26990var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
26991 var BRACE = /{}/g,
26992 IS_WHEN = /^when(Minus)?(.+)$/;
26993
26994 return {
26995 link: function(scope, element, attr) {
26996 var numberExp = attr.count,
26997 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
26998 offset = attr.offset || 0,
26999 whens = scope.$eval(whenExp) || {},
27000 whensExpFns = {},
27001 startSymbol = $interpolate.startSymbol(),
27002 endSymbol = $interpolate.endSymbol(),
27003 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27004 watchRemover = angular.noop,
27005 lastCount;
27006
27007 forEach(attr, function(expression, attributeName) {
27008 var tmpMatch = IS_WHEN.exec(attributeName);
27009 if (tmpMatch) {
27010 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27011 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27012 }
27013 });
27014 forEach(whens, function(expression, key) {
27015 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27016
27017 });
27018
27019 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27020 var count = parseFloat(newVal);
27021 var countIsNaN = isNaN(count);
27022
27023 if (!countIsNaN && !(count in whens)) {
27024 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27025 // Otherwise, check it against pluralization rules in $locale service.
27026 count = $locale.pluralCat(count - offset);
27027 }
27028
27029 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27030 // In JS `NaN !== NaN`, so we have to exlicitly check.
27031 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27032 watchRemover();
27033 var whenExpFn = whensExpFns[count];
27034 if (isUndefined(whenExpFn)) {
27035 if (newVal != null) {
27036 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27037 }
27038 watchRemover = noop;
27039 updateElementText();
27040 } else {
27041 watchRemover = scope.$watch(whenExpFn, updateElementText);
27042 }
27043 lastCount = count;
27044 }
27045 });
27046
27047 function updateElementText(newText) {
27048 element.text(newText || '');
27049 }
27050 }
27051 };
27052}];
27053
27054/**
27055 * @ngdoc directive
27056 * @name ngRepeat
27057 * @multiElement
27058 *
27059 * @description
27060 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27061 * instance gets its own scope, where the given loop variable is set to the current collection item,
27062 * and `$index` is set to the item index or key.
27063 *
27064 * Special properties are exposed on the local scope of each template instance, including:
27065 *
27066 * | Variable | Type | Details |
27067 * |-----------|-----------------|-----------------------------------------------------------------------------|
27068 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27069 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27070 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27071 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27072 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27073 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27074 *
27075 * <div class="alert alert-info">
27076 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27077 * This may be useful when, for instance, nesting ngRepeats.
27078 * </div>
27079 *
27080 *
27081 * # Iterating over object properties
27082 *
27083 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27084 * syntax:
27085 *
27086 * ```js
27087 * <div ng-repeat="(key, value) in myObj"> ... </div>
27088 * ```
27089 *
27090 * You need to be aware that the JavaScript specification does not define the order of keys
27091 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27092 * used to sort the keys alphabetically.)
27093 *
27094 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27095 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27096 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27097 * and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
27098 *
27099 * If this is not desired, the recommended workaround is to convert your object into an array
27100 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27101 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27102 * or implement a `$watch` on the object yourself.
27103 *
27104 *
27105 * # Tracking and Duplicates
27106 *
27107 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27108 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27109 *
27110 * * When an item is added, a new instance of the template is added to the DOM.
27111 * * When an item is removed, its template instance is removed from the DOM.
27112 * * When items are reordered, their respective templates are reordered in the DOM.
27113 *
27114 * To minimize creation of DOM elements, `ngRepeat` uses a function
27115 * to "keep track" of all items in the collection and their corresponding DOM elements.
27116 * For example, if an item is added to the collection, ngRepeat will know that all other items
27117 * already have DOM elements, and will not re-render them.
27118 *
27119 * The default tracking function (which tracks items by their identity) does not allow
27120 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27121 * to maintain a one-to-one mapping between collection items and DOM elements.
27122 *
27123 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27124 * with your own using the `track by` expression.
27125 *
27126 * For example, you may track items by the index of each item in the collection, using the
27127 * special scope property `$index`:
27128 * ```html
27129 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27130 * {{n}}
27131 * </div>
27132 * ```
27133 *
27134 * You may also use arbitrary expressions in `track by`, including references to custom functions
27135 * on the scope:
27136 * ```html
27137 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27138 * {{n}}
27139 * </div>
27140 * ```
27141 *
27142 * <div class="alert alert-success">
27143 * If you are working with objects that have an identifier property, you should track
27144 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27145 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27146 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27147 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27148 * `track by $index` can also provide a performance boost.
27149 * </div>
27150 * ```html
27151 * <div ng-repeat="model in collection track by model.id">
27152 * {{model.name}}
27153 * </div>
27154 * ```
27155 *
27156 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27157 * `$id` function, which tracks items by their identity:
27158 * ```html
27159 * <div ng-repeat="obj in collection track by $id(obj)">
27160 * {{obj.prop}}
27161 * </div>
27162 * ```
27163 *
27164 * <div class="alert alert-warning">
27165 * **Note:** `track by` must always be the last expression:
27166 * </div>
27167 * ```
27168 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27169 * {{model.name}}
27170 * </div>
27171 * ```
27172 *
27173 * # Special repeat start and end points
27174 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27175 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27176 * 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)
27177 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27178 *
27179 * The example below makes use of this feature:
27180 * ```html
27181 * <header ng-repeat-start="item in items">
27182 * Header {{ item }}
27183 * </header>
27184 * <div class="body">
27185 * Body {{ item }}
27186 * </div>
27187 * <footer ng-repeat-end>
27188 * Footer {{ item }}
27189 * </footer>
27190 * ```
27191 *
27192 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27193 * ```html
27194 * <header>
27195 * Header A
27196 * </header>
27197 * <div class="body">
27198 * Body A
27199 * </div>
27200 * <footer>
27201 * Footer A
27202 * </footer>
27203 * <header>
27204 * Header B
27205 * </header>
27206 * <div class="body">
27207 * Body B
27208 * </div>
27209 * <footer>
27210 * Footer B
27211 * </footer>
27212 * ```
27213 *
27214 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27215 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27216 *
27217 * @animations
27218 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27219 *
27220 * **.leave** - when an item is removed from the list or when an item is filtered out
27221 *
27222 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27223 *
27224 * @element ANY
27225 * @scope
27226 * @priority 1000
27227 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27228 * formats are currently supported:
27229 *
27230 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27231 * is a scope expression giving the collection to enumerate.
27232 *
27233 * For example: `album in artist.albums`.
27234 *
27235 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27236 * and `expression` is the scope expression giving the collection to enumerate.
27237 *
27238 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27239 *
27240 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27241 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27242 * is specified, ng-repeat associates elements by identity. It is an error to have
27243 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27244 * mapped to the same DOM element, which is not possible.)
27245 *
27246 * Note that the tracking expression must come last, after any filters, and the alias expression.
27247 *
27248 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27249 * will be associated by item identity in the array.
27250 *
27251 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27252 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27253 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27254 * element in the same way in the DOM.
27255 *
27256 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27257 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27258 * property is same.
27259 *
27260 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27261 * to items in conjunction with a tracking expression.
27262 *
27263 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27264 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27265 * when a filter is active on the repeater, but the filtered result set is empty.
27266 *
27267 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27268 * the items have been processed through the filter.
27269 *
27270 * 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
27271 * (and not as operator, inside an expression).
27272 *
27273 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27274 *
27275 * @example
27276 * This example initializes the scope to a list of names and
27277 * then uses `ngRepeat` to display every person:
27278 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27279 <file name="index.html">
27280 <div ng-init="friends = [
27281 {name:'John', age:25, gender:'boy'},
27282 {name:'Jessie', age:30, gender:'girl'},
27283 {name:'Johanna', age:28, gender:'girl'},
27284 {name:'Joy', age:15, gender:'girl'},
27285 {name:'Mary', age:28, gender:'girl'},
27286 {name:'Peter', age:95, gender:'boy'},
27287 {name:'Sebastian', age:50, gender:'boy'},
27288 {name:'Erika', age:27, gender:'girl'},
27289 {name:'Patrick', age:40, gender:'boy'},
27290 {name:'Samantha', age:60, gender:'girl'}
27291 ]">
27292 I have {{friends.length}} friends. They are:
27293 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27294 <ul class="example-animate-container">
27295 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27296 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27297 </li>
27298 <li class="animate-repeat" ng-if="results.length == 0">
27299 <strong>No results found...</strong>
27300 </li>
27301 </ul>
27302 </div>
27303 </file>
27304 <file name="animations.css">
27305 .example-animate-container {
27306 background:white;
27307 border:1px solid black;
27308 list-style:none;
27309 margin:0;
27310 padding:0 10px;
27311 }
27312
27313 .animate-repeat {
27314 line-height:40px;
27315 list-style:none;
27316 box-sizing:border-box;
27317 }
27318
27319 .animate-repeat.ng-move,
27320 .animate-repeat.ng-enter,
27321 .animate-repeat.ng-leave {
27322 transition:all linear 0.5s;
27323 }
27324
27325 .animate-repeat.ng-leave.ng-leave-active,
27326 .animate-repeat.ng-move,
27327 .animate-repeat.ng-enter {
27328 opacity:0;
27329 max-height:0;
27330 }
27331
27332 .animate-repeat.ng-leave,
27333 .animate-repeat.ng-move.ng-move-active,
27334 .animate-repeat.ng-enter.ng-enter-active {
27335 opacity:1;
27336 max-height:40px;
27337 }
27338 </file>
27339 <file name="protractor.js" type="protractor">
27340 var friends = element.all(by.repeater('friend in friends'));
27341
27342 it('should render initial data set', function() {
27343 expect(friends.count()).toBe(10);
27344 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27345 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27346 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27347 expect(element(by.binding('friends.length')).getText())
27348 .toMatch("I have 10 friends. They are:");
27349 });
27350
27351 it('should update repeater when filter predicate changes', function() {
27352 expect(friends.count()).toBe(10);
27353
27354 element(by.model('q')).sendKeys('ma');
27355
27356 expect(friends.count()).toBe(2);
27357 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27358 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27359 });
27360 </file>
27361 </example>
27362 */
27363var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27364 var NG_REMOVED = '$$NG_REMOVED';
27365 var ngRepeatMinErr = minErr('ngRepeat');
27366
27367 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27368 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27369 scope[valueIdentifier] = value;
27370 if (keyIdentifier) scope[keyIdentifier] = key;
27371 scope.$index = index;
27372 scope.$first = (index === 0);
27373 scope.$last = (index === (arrayLength - 1));
27374 scope.$middle = !(scope.$first || scope.$last);
27375 // jshint bitwise: false
27376 scope.$odd = !(scope.$even = (index&1) === 0);
27377 // jshint bitwise: true
27378 };
27379
27380 var getBlockStart = function(block) {
27381 return block.clone[0];
27382 };
27383
27384 var getBlockEnd = function(block) {
27385 return block.clone[block.clone.length - 1];
27386 };
27387
27388
27389 return {
27390 restrict: 'A',
27391 multiElement: true,
27392 transclude: 'element',
27393 priority: 1000,
27394 terminal: true,
27395 $$tlb: true,
27396 compile: function ngRepeatCompile($element, $attr) {
27397 var expression = $attr.ngRepeat;
27398 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27399
27400 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*$/);
27401
27402 if (!match) {
27403 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27404 expression);
27405 }
27406
27407 var lhs = match[1];
27408 var rhs = match[2];
27409 var aliasAs = match[3];
27410 var trackByExp = match[4];
27411
27412 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27413
27414 if (!match) {
27415 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27416 lhs);
27417 }
27418 var valueIdentifier = match[3] || match[1];
27419 var keyIdentifier = match[2];
27420
27421 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27422 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27423 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27424 aliasAs);
27425 }
27426
27427 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27428 var hashFnLocals = {$id: hashKey};
27429
27430 if (trackByExp) {
27431 trackByExpGetter = $parse(trackByExp);
27432 } else {
27433 trackByIdArrayFn = function(key, value) {
27434 return hashKey(value);
27435 };
27436 trackByIdObjFn = function(key) {
27437 return key;
27438 };
27439 }
27440
27441 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27442
27443 if (trackByExpGetter) {
27444 trackByIdExpFn = function(key, value, index) {
27445 // assign key, value, and $index to the locals so that they can be used in hash functions
27446 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27447 hashFnLocals[valueIdentifier] = value;
27448 hashFnLocals.$index = index;
27449 return trackByExpGetter($scope, hashFnLocals);
27450 };
27451 }
27452
27453 // Store a list of elements from previous run. This is a hash where key is the item from the
27454 // iterator, and the value is objects with following properties.
27455 // - scope: bound scope
27456 // - element: previous element.
27457 // - index: position
27458 //
27459 // We are using no-proto object so that we don't need to guard against inherited props via
27460 // hasOwnProperty.
27461 var lastBlockMap = createMap();
27462
27463 //watch props
27464 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27465 var index, length,
27466 previousNode = $element[0], // node that cloned nodes should be inserted after
27467 // initialized to the comment node anchor
27468 nextNode,
27469 // Same as lastBlockMap but it has the current state. It will become the
27470 // lastBlockMap on the next iteration.
27471 nextBlockMap = createMap(),
27472 collectionLength,
27473 key, value, // key/value of iteration
27474 trackById,
27475 trackByIdFn,
27476 collectionKeys,
27477 block, // last object information {scope, element, id}
27478 nextBlockOrder,
27479 elementsToRemove;
27480
27481 if (aliasAs) {
27482 $scope[aliasAs] = collection;
27483 }
27484
27485 if (isArrayLike(collection)) {
27486 collectionKeys = collection;
27487 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27488 } else {
27489 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27490 // if object, extract keys, in enumeration order, unsorted
27491 collectionKeys = [];
27492 for (var itemKey in collection) {
27493 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27494 collectionKeys.push(itemKey);
27495 }
27496 }
27497 }
27498
27499 collectionLength = collectionKeys.length;
27500 nextBlockOrder = new Array(collectionLength);
27501
27502 // locate existing items
27503 for (index = 0; index < collectionLength; index++) {
27504 key = (collection === collectionKeys) ? index : collectionKeys[index];
27505 value = collection[key];
27506 trackById = trackByIdFn(key, value, index);
27507 if (lastBlockMap[trackById]) {
27508 // found previously seen block
27509 block = lastBlockMap[trackById];
27510 delete lastBlockMap[trackById];
27511 nextBlockMap[trackById] = block;
27512 nextBlockOrder[index] = block;
27513 } else if (nextBlockMap[trackById]) {
27514 // if collision detected. restore lastBlockMap and throw an error
27515 forEach(nextBlockOrder, function(block) {
27516 if (block && block.scope) lastBlockMap[block.id] = block;
27517 });
27518 throw ngRepeatMinErr('dupes',
27519 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27520 expression, trackById, value);
27521 } else {
27522 // new never before seen block
27523 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27524 nextBlockMap[trackById] = true;
27525 }
27526 }
27527
27528 // remove leftover items
27529 for (var blockKey in lastBlockMap) {
27530 block = lastBlockMap[blockKey];
27531 elementsToRemove = getBlockNodes(block.clone);
27532 $animate.leave(elementsToRemove);
27533 if (elementsToRemove[0].parentNode) {
27534 // if the element was not removed yet because of pending animation, mark it as deleted
27535 // so that we can ignore it later
27536 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27537 elementsToRemove[index][NG_REMOVED] = true;
27538 }
27539 }
27540 block.scope.$destroy();
27541 }
27542
27543 // we are not using forEach for perf reasons (trying to avoid #call)
27544 for (index = 0; index < collectionLength; index++) {
27545 key = (collection === collectionKeys) ? index : collectionKeys[index];
27546 value = collection[key];
27547 block = nextBlockOrder[index];
27548
27549 if (block.scope) {
27550 // if we have already seen this object, then we need to reuse the
27551 // associated scope/element
27552
27553 nextNode = previousNode;
27554
27555 // skip nodes that are already pending removal via leave animation
27556 do {
27557 nextNode = nextNode.nextSibling;
27558 } while (nextNode && nextNode[NG_REMOVED]);
27559
27560 if (getBlockStart(block) != nextNode) {
27561 // existing item which got moved
27562 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27563 }
27564 previousNode = getBlockEnd(block);
27565 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27566 } else {
27567 // new item which we don't know about
27568 $transclude(function ngRepeatTransclude(clone, scope) {
27569 block.scope = scope;
27570 // http://jsperf.com/clone-vs-createcomment
27571 var endNode = ngRepeatEndComment.cloneNode(false);
27572 clone[clone.length++] = endNode;
27573
27574 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27575 $animate.enter(clone, null, jqLite(previousNode));
27576 previousNode = endNode;
27577 // Note: We only need the first/last node of the cloned nodes.
27578 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27579 // by a directive with templateUrl when its template arrives.
27580 block.clone = clone;
27581 nextBlockMap[block.id] = block;
27582 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27583 });
27584 }
27585 }
27586 lastBlockMap = nextBlockMap;
27587 });
27588 };
27589 }
27590 };
27591}];
27592
27593var NG_HIDE_CLASS = 'ng-hide';
27594var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27595/**
27596 * @ngdoc directive
27597 * @name ngShow
27598 * @multiElement
27599 *
27600 * @description
27601 * The `ngShow` directive shows or hides the given HTML element based on the expression
27602 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27603 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27604 * in AngularJS and sets the display style to none (using an !important flag).
27605 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27606 *
27607 * ```html
27608 * <!-- when $scope.myValue is truthy (element is visible) -->
27609 * <div ng-show="myValue"></div>
27610 *
27611 * <!-- when $scope.myValue is falsy (element is hidden) -->
27612 * <div ng-show="myValue" class="ng-hide"></div>
27613 * ```
27614 *
27615 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27616 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27617 * from the element causing the element not to appear hidden.
27618 *
27619 * ## Why is !important used?
27620 *
27621 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27622 * can be easily overridden by heavier selectors. For example, something as simple
27623 * as changing the display style on a HTML list item would make hidden elements appear visible.
27624 * This also becomes a bigger issue when dealing with CSS frameworks.
27625 *
27626 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27627 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27628 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27629 *
27630 * ### Overriding `.ng-hide`
27631 *
27632 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27633 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27634 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27635 * with extra animation classes that can be added.
27636 *
27637 * ```css
27638 * .ng-hide:not(.ng-hide-animate) {
27639 * /&#42; this is just another form of hiding an element &#42;/
27640 * display: block!important;
27641 * position: absolute;
27642 * top: -9999px;
27643 * left: -9999px;
27644 * }
27645 * ```
27646 *
27647 * By default you don't need to override in CSS anything and the animations will work around the display style.
27648 *
27649 * ## A note about animations with `ngShow`
27650 *
27651 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27652 * is true and false. This system works like the animation system present with ngClass except that
27653 * you must also include the !important flag to override the display property
27654 * so that you can perform an animation when the element is hidden during the time of the animation.
27655 *
27656 * ```css
27657 * //
27658 * //a working example can be found at the bottom of this page
27659 * //
27660 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27661 * /&#42; this is required as of 1.3x to properly
27662 * apply all styling in a show/hide animation &#42;/
27663 * transition: 0s linear all;
27664 * }
27665 *
27666 * .my-element.ng-hide-add-active,
27667 * .my-element.ng-hide-remove-active {
27668 * /&#42; the transition is defined in the active class &#42;/
27669 * transition: 1s linear all;
27670 * }
27671 *
27672 * .my-element.ng-hide-add { ... }
27673 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27674 * .my-element.ng-hide-remove { ... }
27675 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27676 * ```
27677 *
27678 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27679 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27680 *
27681 * @animations
27682 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27683 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27684 *
27685 * @element ANY
27686 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27687 * then the element is shown or hidden respectively.
27688 *
27689 * @example
27690 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27691 <file name="index.html">
27692 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27693 <div>
27694 Show:
27695 <div class="check-element animate-show" ng-show="checked">
27696 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27697 </div>
27698 </div>
27699 <div>
27700 Hide:
27701 <div class="check-element animate-show" ng-hide="checked">
27702 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27703 </div>
27704 </div>
27705 </file>
27706 <file name="glyphicons.css">
27707 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27708 </file>
27709 <file name="animations.css">
27710 .animate-show {
27711 line-height: 20px;
27712 opacity: 1;
27713 padding: 10px;
27714 border: 1px solid black;
27715 background: white;
27716 }
27717
27718 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27719 transition: all linear 0.5s;
27720 }
27721
27722 .animate-show.ng-hide {
27723 line-height: 0;
27724 opacity: 0;
27725 padding: 0 10px;
27726 }
27727
27728 .check-element {
27729 padding: 10px;
27730 border: 1px solid black;
27731 background: white;
27732 }
27733 </file>
27734 <file name="protractor.js" type="protractor">
27735 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27736 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27737
27738 it('should check ng-show / ng-hide', function() {
27739 expect(thumbsUp.isDisplayed()).toBeFalsy();
27740 expect(thumbsDown.isDisplayed()).toBeTruthy();
27741
27742 element(by.model('checked')).click();
27743
27744 expect(thumbsUp.isDisplayed()).toBeTruthy();
27745 expect(thumbsDown.isDisplayed()).toBeFalsy();
27746 });
27747 </file>
27748 </example>
27749 */
27750var ngShowDirective = ['$animate', function($animate) {
27751 return {
27752 restrict: 'A',
27753 multiElement: true,
27754 link: function(scope, element, attr) {
27755 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27756 // we're adding a temporary, animation-specific class for ng-hide since this way
27757 // we can control when the element is actually displayed on screen without having
27758 // to have a global/greedy CSS selector that breaks when other animations are run.
27759 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27760 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27761 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27762 });
27763 });
27764 }
27765 };
27766}];
27767
27768
27769/**
27770 * @ngdoc directive
27771 * @name ngHide
27772 * @multiElement
27773 *
27774 * @description
27775 * The `ngHide` directive shows or hides the given HTML element based on the expression
27776 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27777 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27778 * in AngularJS and sets the display style to none (using an !important flag).
27779 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27780 *
27781 * ```html
27782 * <!-- when $scope.myValue is truthy (element is hidden) -->
27783 * <div ng-hide="myValue" class="ng-hide"></div>
27784 *
27785 * <!-- when $scope.myValue is falsy (element is visible) -->
27786 * <div ng-hide="myValue"></div>
27787 * ```
27788 *
27789 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27790 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27791 * from the element causing the element not to appear hidden.
27792 *
27793 * ## Why is !important used?
27794 *
27795 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27796 * can be easily overridden by heavier selectors. For example, something as simple
27797 * as changing the display style on a HTML list item would make hidden elements appear visible.
27798 * This also becomes a bigger issue when dealing with CSS frameworks.
27799 *
27800 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27801 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27802 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27803 *
27804 * ### Overriding `.ng-hide`
27805 *
27806 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27807 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27808 * class in CSS:
27809 *
27810 * ```css
27811 * .ng-hide {
27812 * /&#42; this is just another form of hiding an element &#42;/
27813 * display: block!important;
27814 * position: absolute;
27815 * top: -9999px;
27816 * left: -9999px;
27817 * }
27818 * ```
27819 *
27820 * By default you don't need to override in CSS anything and the animations will work around the display style.
27821 *
27822 * ## A note about animations with `ngHide`
27823 *
27824 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27825 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27826 * CSS class is added and removed for you instead of your own CSS class.
27827 *
27828 * ```css
27829 * //
27830 * //a working example can be found at the bottom of this page
27831 * //
27832 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27833 * transition: 0.5s linear all;
27834 * }
27835 *
27836 * .my-element.ng-hide-add { ... }
27837 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27838 * .my-element.ng-hide-remove { ... }
27839 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27840 * ```
27841 *
27842 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27843 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27844 *
27845 * @animations
27846 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27847 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27848 *
27849 * @element ANY
27850 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27851 * the element is shown or hidden respectively.
27852 *
27853 * @example
27854 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27855 <file name="index.html">
27856 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27857 <div>
27858 Show:
27859 <div class="check-element animate-hide" ng-show="checked">
27860 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27861 </div>
27862 </div>
27863 <div>
27864 Hide:
27865 <div class="check-element animate-hide" ng-hide="checked">
27866 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27867 </div>
27868 </div>
27869 </file>
27870 <file name="glyphicons.css">
27871 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27872 </file>
27873 <file name="animations.css">
27874 .animate-hide {
27875 transition: all linear 0.5s;
27876 line-height: 20px;
27877 opacity: 1;
27878 padding: 10px;
27879 border: 1px solid black;
27880 background: white;
27881 }
27882
27883 .animate-hide.ng-hide {
27884 line-height: 0;
27885 opacity: 0;
27886 padding: 0 10px;
27887 }
27888
27889 .check-element {
27890 padding: 10px;
27891 border: 1px solid black;
27892 background: white;
27893 }
27894 </file>
27895 <file name="protractor.js" type="protractor">
27896 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27897 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27898
27899 it('should check ng-show / ng-hide', function() {
27900 expect(thumbsUp.isDisplayed()).toBeFalsy();
27901 expect(thumbsDown.isDisplayed()).toBeTruthy();
27902
27903 element(by.model('checked')).click();
27904
27905 expect(thumbsUp.isDisplayed()).toBeTruthy();
27906 expect(thumbsDown.isDisplayed()).toBeFalsy();
27907 });
27908 </file>
27909 </example>
27910 */
27911var ngHideDirective = ['$animate', function($animate) {
27912 return {
27913 restrict: 'A',
27914 multiElement: true,
27915 link: function(scope, element, attr) {
27916 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27917 // The comment inside of the ngShowDirective explains why we add and
27918 // remove a temporary class for the show/hide animation
27919 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27920 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27921 });
27922 });
27923 }
27924 };
27925}];
27926
27927/**
27928 * @ngdoc directive
27929 * @name ngStyle
27930 * @restrict AC
27931 *
27932 * @description
27933 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
27934 *
27935 * @element ANY
27936 * @param {expression} ngStyle
27937 *
27938 * {@link guide/expression Expression} which evals to an
27939 * object whose keys are CSS style names and values are corresponding values for those CSS
27940 * keys.
27941 *
27942 * Since some CSS style names are not valid keys for an object, they must be quoted.
27943 * See the 'background-color' style in the example below.
27944 *
27945 * @example
27946 <example>
27947 <file name="index.html">
27948 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
27949 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
27950 <input type="button" value="clear" ng-click="myStyle={}">
27951 <br/>
27952 <span ng-style="myStyle">Sample Text</span>
27953 <pre>myStyle={{myStyle}}</pre>
27954 </file>
27955 <file name="style.css">
27956 span {
27957 color: black;
27958 }
27959 </file>
27960 <file name="protractor.js" type="protractor">
27961 var colorSpan = element(by.css('span'));
27962
27963 it('should check ng-style', function() {
27964 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
27965 element(by.css('input[value=\'set color\']')).click();
27966 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
27967 element(by.css('input[value=clear]')).click();
27968 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
27969 });
27970 </file>
27971 </example>
27972 */
27973var ngStyleDirective = ngDirective(function(scope, element, attr) {
27974 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
27975 if (oldStyles && (newStyles !== oldStyles)) {
27976 forEach(oldStyles, function(val, style) { element.css(style, '');});
27977 }
27978 if (newStyles) element.css(newStyles);
27979 }, true);
27980});
27981
27982/**
27983 * @ngdoc directive
27984 * @name ngSwitch
27985 * @restrict EA
27986 *
27987 * @description
27988 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
27989 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
27990 * as specified in the template.
27991 *
27992 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
27993 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
27994 * matches the value obtained from the evaluated expression. In other words, you define a container element
27995 * (where you place the directive), place an expression on the **`on="..."` attribute**
27996 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
27997 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
27998 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
27999 * attribute is displayed.
28000 *
28001 * <div class="alert alert-info">
28002 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28003 * as literal string values to match against.
28004 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28005 * value of the expression `$scope.someVal`.
28006 * </div>
28007
28008 * @animations
28009 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28010 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28011 *
28012 * @usage
28013 *
28014 * ```
28015 * <ANY ng-switch="expression">
28016 * <ANY ng-switch-when="matchValue1">...</ANY>
28017 * <ANY ng-switch-when="matchValue2">...</ANY>
28018 * <ANY ng-switch-default>...</ANY>
28019 * </ANY>
28020 * ```
28021 *
28022 *
28023 * @scope
28024 * @priority 1200
28025 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28026 * On child elements add:
28027 *
28028 * * `ngSwitchWhen`: the case statement to match against. If match then this
28029 * case will be displayed. If the same match appears multiple times, all the
28030 * elements will be displayed.
28031 * * `ngSwitchDefault`: the default case when no other case match. If there
28032 * are multiple default cases, all of them will be displayed when no other
28033 * case match.
28034 *
28035 *
28036 * @example
28037 <example module="switchExample" deps="angular-animate.js" animations="true">
28038 <file name="index.html">
28039 <div ng-controller="ExampleController">
28040 <select ng-model="selection" ng-options="item for item in items">
28041 </select>
28042 <code>selection={{selection}}</code>
28043 <hr/>
28044 <div class="animate-switch-container"
28045 ng-switch on="selection">
28046 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28047 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28048 <div class="animate-switch" ng-switch-default>default</div>
28049 </div>
28050 </div>
28051 </file>
28052 <file name="script.js">
28053 angular.module('switchExample', ['ngAnimate'])
28054 .controller('ExampleController', ['$scope', function($scope) {
28055 $scope.items = ['settings', 'home', 'other'];
28056 $scope.selection = $scope.items[0];
28057 }]);
28058 </file>
28059 <file name="animations.css">
28060 .animate-switch-container {
28061 position:relative;
28062 background:white;
28063 border:1px solid black;
28064 height:40px;
28065 overflow:hidden;
28066 }
28067
28068 .animate-switch {
28069 padding:10px;
28070 }
28071
28072 .animate-switch.ng-animate {
28073 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28074
28075 position:absolute;
28076 top:0;
28077 left:0;
28078 right:0;
28079 bottom:0;
28080 }
28081
28082 .animate-switch.ng-leave.ng-leave-active,
28083 .animate-switch.ng-enter {
28084 top:-50px;
28085 }
28086 .animate-switch.ng-leave,
28087 .animate-switch.ng-enter.ng-enter-active {
28088 top:0;
28089 }
28090 </file>
28091 <file name="protractor.js" type="protractor">
28092 var switchElem = element(by.css('[ng-switch]'));
28093 var select = element(by.model('selection'));
28094
28095 it('should start in settings', function() {
28096 expect(switchElem.getText()).toMatch(/Settings Div/);
28097 });
28098 it('should change to home', function() {
28099 select.all(by.css('option')).get(1).click();
28100 expect(switchElem.getText()).toMatch(/Home Span/);
28101 });
28102 it('should select default', function() {
28103 select.all(by.css('option')).get(2).click();
28104 expect(switchElem.getText()).toMatch(/default/);
28105 });
28106 </file>
28107 </example>
28108 */
28109var ngSwitchDirective = ['$animate', function($animate) {
28110 return {
28111 require: 'ngSwitch',
28112
28113 // asks for $scope to fool the BC controller module
28114 controller: ['$scope', function ngSwitchController() {
28115 this.cases = {};
28116 }],
28117 link: function(scope, element, attr, ngSwitchController) {
28118 var watchExpr = attr.ngSwitch || attr.on,
28119 selectedTranscludes = [],
28120 selectedElements = [],
28121 previousLeaveAnimations = [],
28122 selectedScopes = [];
28123
28124 var spliceFactory = function(array, index) {
28125 return function() { array.splice(index, 1); };
28126 };
28127
28128 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28129 var i, ii;
28130 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28131 $animate.cancel(previousLeaveAnimations[i]);
28132 }
28133 previousLeaveAnimations.length = 0;
28134
28135 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28136 var selected = getBlockNodes(selectedElements[i].clone);
28137 selectedScopes[i].$destroy();
28138 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28139 promise.then(spliceFactory(previousLeaveAnimations, i));
28140 }
28141
28142 selectedElements.length = 0;
28143 selectedScopes.length = 0;
28144
28145 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28146 forEach(selectedTranscludes, function(selectedTransclude) {
28147 selectedTransclude.transclude(function(caseElement, selectedScope) {
28148 selectedScopes.push(selectedScope);
28149 var anchor = selectedTransclude.element;
28150 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28151 var block = { clone: caseElement };
28152
28153 selectedElements.push(block);
28154 $animate.enter(caseElement, anchor.parent(), anchor);
28155 });
28156 });
28157 }
28158 });
28159 }
28160 };
28161}];
28162
28163var ngSwitchWhenDirective = ngDirective({
28164 transclude: 'element',
28165 priority: 1200,
28166 require: '^ngSwitch',
28167 multiElement: true,
28168 link: function(scope, element, attrs, ctrl, $transclude) {
28169 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28170 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28171 }
28172});
28173
28174var ngSwitchDefaultDirective = ngDirective({
28175 transclude: 'element',
28176 priority: 1200,
28177 require: '^ngSwitch',
28178 multiElement: true,
28179 link: function(scope, element, attr, ctrl, $transclude) {
28180 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28181 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28182 }
28183});
28184
28185/**
28186 * @ngdoc directive
28187 * @name ngTransclude
28188 * @restrict EAC
28189 *
28190 * @description
28191 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28192 *
28193 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28194 *
28195 * @element ANY
28196 *
28197 * @example
28198 <example module="transcludeExample">
28199 <file name="index.html">
28200 <script>
28201 angular.module('transcludeExample', [])
28202 .directive('pane', function(){
28203 return {
28204 restrict: 'E',
28205 transclude: true,
28206 scope: { title:'@' },
28207 template: '<div style="border: 1px solid black;">' +
28208 '<div style="background-color: gray">{{title}}</div>' +
28209 '<ng-transclude></ng-transclude>' +
28210 '</div>'
28211 };
28212 })
28213 .controller('ExampleController', ['$scope', function($scope) {
28214 $scope.title = 'Lorem Ipsum';
28215 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28216 }]);
28217 </script>
28218 <div ng-controller="ExampleController">
28219 <input ng-model="title" aria-label="title"> <br/>
28220 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28221 <pane title="{{title}}">{{text}}</pane>
28222 </div>
28223 </file>
28224 <file name="protractor.js" type="protractor">
28225 it('should have transcluded', function() {
28226 var titleElement = element(by.model('title'));
28227 titleElement.clear();
28228 titleElement.sendKeys('TITLE');
28229 var textElement = element(by.model('text'));
28230 textElement.clear();
28231 textElement.sendKeys('TEXT');
28232 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28233 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28234 });
28235 </file>
28236 </example>
28237 *
28238 */
28239var ngTranscludeDirective = ngDirective({
28240 restrict: 'EAC',
28241 link: function($scope, $element, $attrs, controller, $transclude) {
28242 if (!$transclude) {
28243 throw minErr('ngTransclude')('orphan',
28244 'Illegal use of ngTransclude directive in the template! ' +
28245 'No parent directive that requires a transclusion found. ' +
28246 'Element: {0}',
28247 startingTag($element));
28248 }
28249
28250 $transclude(function(clone) {
28251 $element.empty();
28252 $element.append(clone);
28253 });
28254 }
28255});
28256
28257/**
28258 * @ngdoc directive
28259 * @name script
28260 * @restrict E
28261 *
28262 * @description
28263 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28264 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28265 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28266 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28267 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28268 *
28269 * @param {string} type Must be set to `'text/ng-template'`.
28270 * @param {string} id Cache name of the template.
28271 *
28272 * @example
28273 <example>
28274 <file name="index.html">
28275 <script type="text/ng-template" id="/tpl.html">
28276 Content of the template.
28277 </script>
28278
28279 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28280 <div id="tpl-content" ng-include src="currentTpl"></div>
28281 </file>
28282 <file name="protractor.js" type="protractor">
28283 it('should load template defined inside script tag', function() {
28284 element(by.css('#tpl-link')).click();
28285 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28286 });
28287 </file>
28288 </example>
28289 */
28290var scriptDirective = ['$templateCache', function($templateCache) {
28291 return {
28292 restrict: 'E',
28293 terminal: true,
28294 compile: function(element, attr) {
28295 if (attr.type == 'text/ng-template') {
28296 var templateUrl = attr.id,
28297 text = element[0].text;
28298
28299 $templateCache.put(templateUrl, text);
28300 }
28301 }
28302 };
28303}];
28304
28305var noopNgModelController = { $setViewValue: noop, $render: noop };
28306
28307function chromeHack(optionElement) {
28308 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28309 // Adding an <option selected="selected"> element to a <select required="required"> should
28310 // automatically select the new element
28311 if (optionElement[0].hasAttribute('selected')) {
28312 optionElement[0].selected = true;
28313 }
28314}
28315
28316/**
28317 * @ngdoc type
28318 * @name select.SelectController
28319 * @description
28320 * The controller for the `<select>` directive. This provides support for reading
28321 * and writing the selected value(s) of the control and also coordinates dynamically
28322 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28323 */
28324var SelectController =
28325 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28326
28327 var self = this,
28328 optionsMap = new HashMap();
28329
28330 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28331 self.ngModelCtrl = noopNgModelController;
28332
28333 // The "unknown" option is one that is prepended to the list if the viewValue
28334 // does not match any of the options. When it is rendered the value of the unknown
28335 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28336 //
28337 // We can't just jqLite('<option>') since jqLite is not smart enough
28338 // to create it in <select> and IE barfs otherwise.
28339 self.unknownOption = jqLite(document.createElement('option'));
28340 self.renderUnknownOption = function(val) {
28341 var unknownVal = '? ' + hashKey(val) + ' ?';
28342 self.unknownOption.val(unknownVal);
28343 $element.prepend(self.unknownOption);
28344 $element.val(unknownVal);
28345 };
28346
28347 $scope.$on('$destroy', function() {
28348 // disable unknown option so that we don't do work when the whole select is being destroyed
28349 self.renderUnknownOption = noop;
28350 });
28351
28352 self.removeUnknownOption = function() {
28353 if (self.unknownOption.parent()) self.unknownOption.remove();
28354 };
28355
28356
28357 // Read the value of the select control, the implementation of this changes depending
28358 // upon whether the select can have multiple values and whether ngOptions is at work.
28359 self.readValue = function readSingleValue() {
28360 self.removeUnknownOption();
28361 return $element.val();
28362 };
28363
28364
28365 // Write the value to the select control, the implementation of this changes depending
28366 // upon whether the select can have multiple values and whether ngOptions is at work.
28367 self.writeValue = function writeSingleValue(value) {
28368 if (self.hasOption(value)) {
28369 self.removeUnknownOption();
28370 $element.val(value);
28371 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28372 } else {
28373 if (value == null && self.emptyOption) {
28374 self.removeUnknownOption();
28375 $element.val('');
28376 } else {
28377 self.renderUnknownOption(value);
28378 }
28379 }
28380 };
28381
28382
28383 // Tell the select control that an option, with the given value, has been added
28384 self.addOption = function(value, element) {
28385 assertNotHasOwnProperty(value, '"option value"');
28386 if (value === '') {
28387 self.emptyOption = element;
28388 }
28389 var count = optionsMap.get(value) || 0;
28390 optionsMap.put(value, count + 1);
28391 self.ngModelCtrl.$render();
28392 chromeHack(element);
28393 };
28394
28395 // Tell the select control that an option, with the given value, has been removed
28396 self.removeOption = function(value) {
28397 var count = optionsMap.get(value);
28398 if (count) {
28399 if (count === 1) {
28400 optionsMap.remove(value);
28401 if (value === '') {
28402 self.emptyOption = undefined;
28403 }
28404 } else {
28405 optionsMap.put(value, count - 1);
28406 }
28407 }
28408 };
28409
28410 // Check whether the select control has an option matching the given value
28411 self.hasOption = function(value) {
28412 return !!optionsMap.get(value);
28413 };
28414
28415
28416 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28417
28418 if (interpolateValueFn) {
28419 // The value attribute is interpolated
28420 var oldVal;
28421 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28422 if (isDefined(oldVal)) {
28423 self.removeOption(oldVal);
28424 }
28425 oldVal = newVal;
28426 self.addOption(newVal, optionElement);
28427 });
28428 } else if (interpolateTextFn) {
28429 // The text content is interpolated
28430 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28431 optionAttrs.$set('value', newVal);
28432 if (oldVal !== newVal) {
28433 self.removeOption(oldVal);
28434 }
28435 self.addOption(newVal, optionElement);
28436 });
28437 } else {
28438 // The value attribute is static
28439 self.addOption(optionAttrs.value, optionElement);
28440 }
28441
28442 optionElement.on('$destroy', function() {
28443 self.removeOption(optionAttrs.value);
28444 self.ngModelCtrl.$render();
28445 });
28446 };
28447}];
28448
28449/**
28450 * @ngdoc directive
28451 * @name select
28452 * @restrict E
28453 *
28454 * @description
28455 * HTML `SELECT` element with angular data-binding.
28456 *
28457 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28458 * between the scope and the `<select>` control (including setting default values).
28459 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28460 * {@link ngOptions `ngOptions`} directives.
28461 *
28462 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28463 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28464 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28465 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28466 *
28467 * <div class="alert alert-warning">
28468 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28469 * When the model needs to be bound to a non-string value, you must either explictly convert it
28470 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28471 * This is because an option element can only be bound to string values at present.
28472 * </div>
28473 *
28474 * If the viewValue of `ngModel` does not match any of the options, then the control
28475 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28476 *
28477 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28478 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28479 * option. See example below for demonstration.
28480 *
28481 * <div class="alert alert-info">
28482 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28483 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28484 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28485 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28486 * a new scope for each repeated instance.
28487 * </div>
28488 *
28489 *
28490 * @param {string} ngModel Assignable angular expression to data-bind to.
28491 * @param {string=} name Property name of the form under which the control is published.
28492 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28493 * bound to the model as an array.
28494 * @param {string=} required Sets `required` validation error key if the value is not entered.
28495 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28496 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28497 * when you want to data-bind to the required attribute.
28498 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28499 * interaction with the select element.
28500 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28501 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28502 *
28503 * @example
28504 * ### Simple `select` elements with static options
28505 *
28506 * <example name="static-select" module="staticSelect">
28507 * <file name="index.html">
28508 * <div ng-controller="ExampleController">
28509 * <form name="myForm">
28510 * <label for="singleSelect"> Single select: </label><br>
28511 * <select name="singleSelect" ng-model="data.singleSelect">
28512 * <option value="option-1">Option 1</option>
28513 * <option value="option-2">Option 2</option>
28514 * </select><br>
28515 *
28516 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28517 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28518 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28519 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28520 * <option value="option-2">Option 2</option>
28521 * </select><br>
28522 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28523 * <tt>singleSelect = {{data.singleSelect}}</tt>
28524 *
28525 * <hr>
28526 * <label for="multipleSelect"> Multiple select: </label><br>
28527 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28528 * <option value="option-1">Option 1</option>
28529 * <option value="option-2">Option 2</option>
28530 * <option value="option-3">Option 3</option>
28531 * </select><br>
28532 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28533 * </form>
28534 * </div>
28535 * </file>
28536 * <file name="app.js">
28537 * angular.module('staticSelect', [])
28538 * .controller('ExampleController', ['$scope', function($scope) {
28539 * $scope.data = {
28540 * singleSelect: null,
28541 * multipleSelect: [],
28542 * option1: 'option-1',
28543 * };
28544 *
28545 * $scope.forceUnknownOption = function() {
28546 * $scope.data.singleSelect = 'nonsense';
28547 * };
28548 * }]);
28549 * </file>
28550 *</example>
28551 *
28552 * ### Using `ngRepeat` to generate `select` options
28553 * <example name="ngrepeat-select" module="ngrepeatSelect">
28554 * <file name="index.html">
28555 * <div ng-controller="ExampleController">
28556 * <form name="myForm">
28557 * <label for="repeatSelect"> Repeat select: </label>
28558 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28559 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28560 * </select>
28561 * </form>
28562 * <hr>
28563 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28564 * </div>
28565 * </file>
28566 * <file name="app.js">
28567 * angular.module('ngrepeatSelect', [])
28568 * .controller('ExampleController', ['$scope', function($scope) {
28569 * $scope.data = {
28570 * repeatSelect: null,
28571 * availableOptions: [
28572 * {id: '1', name: 'Option A'},
28573 * {id: '2', name: 'Option B'},
28574 * {id: '3', name: 'Option C'}
28575 * ],
28576 * };
28577 * }]);
28578 * </file>
28579 *</example>
28580 *
28581 *
28582 * ### Using `select` with `ngOptions` and setting a default value
28583 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28584 *
28585 * <example name="select-with-default-values" module="defaultValueSelect">
28586 * <file name="index.html">
28587 * <div ng-controller="ExampleController">
28588 * <form name="myForm">
28589 * <label for="mySelect">Make a choice:</label>
28590 * <select name="mySelect" id="mySelect"
28591 * ng-options="option.name for option in data.availableOptions track by option.id"
28592 * ng-model="data.selectedOption"></select>
28593 * </form>
28594 * <hr>
28595 * <tt>option = {{data.selectedOption}}</tt><br/>
28596 * </div>
28597 * </file>
28598 * <file name="app.js">
28599 * angular.module('defaultValueSelect', [])
28600 * .controller('ExampleController', ['$scope', function($scope) {
28601 * $scope.data = {
28602 * availableOptions: [
28603 * {id: '1', name: 'Option A'},
28604 * {id: '2', name: 'Option B'},
28605 * {id: '3', name: 'Option C'}
28606 * ],
28607 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28608 * };
28609 * }]);
28610 * </file>
28611 *</example>
28612 *
28613 *
28614 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28615 *
28616 * <example name="select-with-non-string-options" module="nonStringSelect">
28617 * <file name="index.html">
28618 * <select ng-model="model.id" convert-to-number>
28619 * <option value="0">Zero</option>
28620 * <option value="1">One</option>
28621 * <option value="2">Two</option>
28622 * </select>
28623 * {{ model }}
28624 * </file>
28625 * <file name="app.js">
28626 * angular.module('nonStringSelect', [])
28627 * .run(function($rootScope) {
28628 * $rootScope.model = { id: 2 };
28629 * })
28630 * .directive('convertToNumber', function() {
28631 * return {
28632 * require: 'ngModel',
28633 * link: function(scope, element, attrs, ngModel) {
28634 * ngModel.$parsers.push(function(val) {
28635 * return parseInt(val, 10);
28636 * });
28637 * ngModel.$formatters.push(function(val) {
28638 * return '' + val;
28639 * });
28640 * }
28641 * };
28642 * });
28643 * </file>
28644 * <file name="protractor.js" type="protractor">
28645 * it('should initialize to model', function() {
28646 * var select = element(by.css('select'));
28647 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28648 * });
28649 * </file>
28650 * </example>
28651 *
28652 */
28653var selectDirective = function() {
28654
28655 return {
28656 restrict: 'E',
28657 require: ['select', '?ngModel'],
28658 controller: SelectController,
28659 priority: 1,
28660 link: {
28661 pre: selectPreLink
28662 }
28663 };
28664
28665 function selectPreLink(scope, element, attr, ctrls) {
28666
28667 // if ngModel is not defined, we don't need to do anything
28668 var ngModelCtrl = ctrls[1];
28669 if (!ngModelCtrl) return;
28670
28671 var selectCtrl = ctrls[0];
28672
28673 selectCtrl.ngModelCtrl = ngModelCtrl;
28674
28675 // We delegate rendering to the `writeValue` method, which can be changed
28676 // if the select can have multiple selected values or if the options are being
28677 // generated by `ngOptions`
28678 ngModelCtrl.$render = function() {
28679 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28680 };
28681
28682 // When the selected item(s) changes we delegate getting the value of the select control
28683 // to the `readValue` method, which can be changed if the select can have multiple
28684 // selected values or if the options are being generated by `ngOptions`
28685 element.on('change', function() {
28686 scope.$apply(function() {
28687 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28688 });
28689 });
28690
28691 // If the select allows multiple values then we need to modify how we read and write
28692 // values from and to the control; also what it means for the value to be empty and
28693 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28694 // doesn't trigger rendering if only an item in the array changes.
28695 if (attr.multiple) {
28696
28697 // Read value now needs to check each option to see if it is selected
28698 selectCtrl.readValue = function readMultipleValue() {
28699 var array = [];
28700 forEach(element.find('option'), function(option) {
28701 if (option.selected) {
28702 array.push(option.value);
28703 }
28704 });
28705 return array;
28706 };
28707
28708 // Write value now needs to set the selected property of each matching option
28709 selectCtrl.writeValue = function writeMultipleValue(value) {
28710 var items = new HashMap(value);
28711 forEach(element.find('option'), function(option) {
28712 option.selected = isDefined(items.get(option.value));
28713 });
28714 };
28715
28716 // we have to do it on each watch since ngModel watches reference, but
28717 // we need to work of an array, so we need to see if anything was inserted/removed
28718 var lastView, lastViewRef = NaN;
28719 scope.$watch(function selectMultipleWatch() {
28720 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28721 lastView = shallowCopy(ngModelCtrl.$viewValue);
28722 ngModelCtrl.$render();
28723 }
28724 lastViewRef = ngModelCtrl.$viewValue;
28725 });
28726
28727 // If we are a multiple select then value is now a collection
28728 // so the meaning of $isEmpty changes
28729 ngModelCtrl.$isEmpty = function(value) {
28730 return !value || value.length === 0;
28731 };
28732
28733 }
28734 }
28735};
28736
28737
28738// The option directive is purely designed to communicate the existence (or lack of)
28739// of dynamically created (and destroyed) option elements to their containing select
28740// directive via its controller.
28741var optionDirective = ['$interpolate', function($interpolate) {
28742 return {
28743 restrict: 'E',
28744 priority: 100,
28745 compile: function(element, attr) {
28746
28747 if (isDefined(attr.value)) {
28748 // If the value attribute is defined, check if it contains an interpolation
28749 var interpolateValueFn = $interpolate(attr.value, true);
28750 } else {
28751 // If the value attribute is not defined then we fall back to the
28752 // text content of the option element, which may be interpolated
28753 var interpolateTextFn = $interpolate(element.text(), true);
28754 if (!interpolateTextFn) {
28755 attr.$set('value', element.text());
28756 }
28757 }
28758
28759 return function(scope, element, attr) {
28760
28761 // This is an optimization over using ^^ since we don't want to have to search
28762 // all the way to the root of the DOM for every single option element
28763 var selectCtrlName = '$selectController',
28764 parent = element.parent(),
28765 selectCtrl = parent.data(selectCtrlName) ||
28766 parent.parent().data(selectCtrlName); // in case we are in optgroup
28767
28768 if (selectCtrl) {
28769 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28770 }
28771 };
28772 }
28773 };
28774}];
28775
28776var styleDirective = valueFn({
28777 restrict: 'E',
28778 terminal: false
28779});
28780
28781var requiredDirective = function() {
28782 return {
28783 restrict: 'A',
28784 require: '?ngModel',
28785 link: function(scope, elm, attr, ctrl) {
28786 if (!ctrl) return;
28787 attr.required = true; // force truthy in case we are on non input element
28788
28789 ctrl.$validators.required = function(modelValue, viewValue) {
28790 return !attr.required || !ctrl.$isEmpty(viewValue);
28791 };
28792
28793 attr.$observe('required', function() {
28794 ctrl.$validate();
28795 });
28796 }
28797 };
28798};
28799
28800
28801var patternDirective = function() {
28802 return {
28803 restrict: 'A',
28804 require: '?ngModel',
28805 link: function(scope, elm, attr, ctrl) {
28806 if (!ctrl) return;
28807
28808 var regexp, patternExp = attr.ngPattern || attr.pattern;
28809 attr.$observe('pattern', function(regex) {
28810 if (isString(regex) && regex.length > 0) {
28811 regex = new RegExp('^' + regex + '$');
28812 }
28813
28814 if (regex && !regex.test) {
28815 throw minErr('ngPattern')('noregexp',
28816 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28817 regex, startingTag(elm));
28818 }
28819
28820 regexp = regex || undefined;
28821 ctrl.$validate();
28822 });
28823
28824 ctrl.$validators.pattern = function(modelValue, viewValue) {
28825 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28826 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28827 };
28828 }
28829 };
28830};
28831
28832
28833var maxlengthDirective = function() {
28834 return {
28835 restrict: 'A',
28836 require: '?ngModel',
28837 link: function(scope, elm, attr, ctrl) {
28838 if (!ctrl) return;
28839
28840 var maxlength = -1;
28841 attr.$observe('maxlength', function(value) {
28842 var intVal = toInt(value);
28843 maxlength = isNaN(intVal) ? -1 : intVal;
28844 ctrl.$validate();
28845 });
28846 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28847 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28848 };
28849 }
28850 };
28851};
28852
28853var minlengthDirective = function() {
28854 return {
28855 restrict: 'A',
28856 require: '?ngModel',
28857 link: function(scope, elm, attr, ctrl) {
28858 if (!ctrl) return;
28859
28860 var minlength = 0;
28861 attr.$observe('minlength', function(value) {
28862 minlength = toInt(value) || 0;
28863 ctrl.$validate();
28864 });
28865 ctrl.$validators.minlength = function(modelValue, viewValue) {
28866 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28867 };
28868 }
28869 };
28870};
28871
28872if (window.angular.bootstrap) {
28873 //AngularJS is already loaded, so we can return here...
28874 console.log('WARNING: Tried to load angular more than once.');
28875 return;
28876}
28877
28878//try to bind to jquery now so that one can write jqLite(document).ready()
28879//but we will rebind on bootstrap again.
28880bindJQuery();
28881
28882publishExternalAPI(angular);
28883
28884angular.module("ngLocale", [], ["$provide", function($provide) {
28885var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28886function getDecimals(n) {
28887 n = n + '';
28888 var i = n.indexOf('.');
28889 return (i == -1) ? 0 : n.length - i - 1;
28890}
28891
28892function getVF(n, opt_precision) {
28893 var v = opt_precision;
28894
28895 if (undefined === v) {
28896 v = Math.min(getDecimals(n), 3);
28897 }
28898
28899 var base = Math.pow(10, v);
28900 var f = ((n * base) | 0) % base;
28901 return {v: v, f: f};
28902}
28903
28904$provide.value("$locale", {
28905 "DATETIME_FORMATS": {
28906 "AMPMS": [
28907 "AM",
28908 "PM"
28909 ],
28910 "DAY": [
28911 "Sunday",
28912 "Monday",
28913 "Tuesday",
28914 "Wednesday",
28915 "Thursday",
28916 "Friday",
28917 "Saturday"
28918 ],
28919 "ERANAMES": [
28920 "Before Christ",
28921 "Anno Domini"
28922 ],
28923 "ERAS": [
28924 "BC",
28925 "AD"
28926 ],
28927 "FIRSTDAYOFWEEK": 6,
28928 "MONTH": [
28929 "January",
28930 "February",
28931 "March",
28932 "April",
28933 "May",
28934 "June",
28935 "July",
28936 "August",
28937 "September",
28938 "October",
28939 "November",
28940 "December"
28941 ],
28942 "SHORTDAY": [
28943 "Sun",
28944 "Mon",
28945 "Tue",
28946 "Wed",
28947 "Thu",
28948 "Fri",
28949 "Sat"
28950 ],
28951 "SHORTMONTH": [
28952 "Jan",
28953 "Feb",
28954 "Mar",
28955 "Apr",
28956 "May",
28957 "Jun",
28958 "Jul",
28959 "Aug",
28960 "Sep",
28961 "Oct",
28962 "Nov",
28963 "Dec"
28964 ],
28965 "WEEKENDRANGE": [
28966 5,
28967 6
28968 ],
28969 "fullDate": "EEEE, MMMM d, y",
28970 "longDate": "MMMM d, y",
28971 "medium": "MMM d, y h:mm:ss a",
28972 "mediumDate": "MMM d, y",
28973 "mediumTime": "h:mm:ss a",
28974 "short": "M/d/yy h:mm a",
28975 "shortDate": "M/d/yy",
28976 "shortTime": "h:mm a"
28977 },
28978 "NUMBER_FORMATS": {
28979 "CURRENCY_SYM": "$",
28980 "DECIMAL_SEP": ".",
28981 "GROUP_SEP": ",",
28982 "PATTERNS": [
28983 {
28984 "gSize": 3,
28985 "lgSize": 3,
28986 "maxFrac": 3,
28987 "minFrac": 0,
28988 "minInt": 1,
28989 "negPre": "-",
28990 "negSuf": "",
28991 "posPre": "",
28992 "posSuf": ""
28993 },
28994 {
28995 "gSize": 3,
28996 "lgSize": 3,
28997 "maxFrac": 2,
28998 "minFrac": 2,
28999 "minInt": 1,
29000 "negPre": "-\u00a4",
29001 "negSuf": "",
29002 "posPre": "\u00a4",
29003 "posSuf": ""
29004 }
29005 ]
29006 },
29007 "id": "en-us",
29008 "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;}
29009});
29010}]);
29011
29012 jqLite(document).ready(function() {
29013 angularInit(document, bootstrap);
29014 });
29015
29016})(window, document);
29017
29018!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>');