blob: 34a93c1c41ac865b6e080957ce8081c92e90d7f1 [file] [log] [blame]
Matteo Scandolo280dcd32016-05-16 09:59:38 -07001/**
2 * @license AngularJS v1.4.7
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.7/' +
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 if (obj == null || isWindow(obj)) {
271 return false;
272 }
273
274 // Support: iOS 8.2 (not reproducible in simulator)
275 // "length" in obj used to prevent JIT error (gh-11508)
276 var length = "length" in Object(obj) && obj.length;
277
278 if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
279 return true;
280 }
281
282 return isString(obj) || isArray(obj) || length === 0 ||
283 typeof length === 'number' && length > 0 && (length - 1) in obj;
284}
285
286/**
287 * @ngdoc function
288 * @name angular.forEach
289 * @module ng
290 * @kind function
291 *
292 * @description
293 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
294 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
295 * is the value of an object property or an array element, `key` is the object property key or
296 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
297 *
298 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
299 * using the `hasOwnProperty` method.
300 *
301 * Unlike ES262's
302 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
303 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
304 * return the value provided.
305 *
306 ```js
307 var values = {name: 'misko', gender: 'male'};
308 var log = [];
309 angular.forEach(values, function(value, key) {
310 this.push(key + ': ' + value);
311 }, log);
312 expect(log).toEqual(['name: misko', 'gender: male']);
313 ```
314 *
315 * @param {Object|Array} obj Object to iterate over.
316 * @param {Function} iterator Iterator function.
317 * @param {Object=} context Object to become context (`this`) for the iterator function.
318 * @returns {Object|Array} Reference to `obj`.
319 */
320
321function forEach(obj, iterator, context) {
322 var key, length;
323 if (obj) {
324 if (isFunction(obj)) {
325 for (key in obj) {
326 // Need to check if hasOwnProperty exists,
327 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
328 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
329 iterator.call(context, obj[key], key, obj);
330 }
331 }
332 } else if (isArray(obj) || isArrayLike(obj)) {
333 var isPrimitive = typeof obj !== 'object';
334 for (key = 0, length = obj.length; key < length; key++) {
335 if (isPrimitive || key in obj) {
336 iterator.call(context, obj[key], key, obj);
337 }
338 }
339 } else if (obj.forEach && obj.forEach !== forEach) {
340 obj.forEach(iterator, context, obj);
341 } else if (isBlankObject(obj)) {
342 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
343 for (key in obj) {
344 iterator.call(context, obj[key], key, obj);
345 }
346 } else if (typeof obj.hasOwnProperty === 'function') {
347 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
348 for (key in obj) {
349 if (obj.hasOwnProperty(key)) {
350 iterator.call(context, obj[key], key, obj);
351 }
352 }
353 } else {
354 // Slow path for objects which do not have a method `hasOwnProperty`
355 for (key in obj) {
356 if (hasOwnProperty.call(obj, key)) {
357 iterator.call(context, obj[key], key, obj);
358 }
359 }
360 }
361 }
362 return obj;
363}
364
365function forEachSorted(obj, iterator, context) {
366 var keys = Object.keys(obj).sort();
367 for (var i = 0; i < keys.length; i++) {
368 iterator.call(context, obj[keys[i]], keys[i]);
369 }
370 return keys;
371}
372
373
374/**
375 * when using forEach the params are value, key, but it is often useful to have key, value.
376 * @param {function(string, *)} iteratorFn
377 * @returns {function(*, string)}
378 */
379function reverseParams(iteratorFn) {
380 return function(value, key) { iteratorFn(key, value); };
381}
382
383/**
384 * A consistent way of creating unique IDs in angular.
385 *
386 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
387 * we hit number precision issues in JavaScript.
388 *
389 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
390 *
391 * @returns {number} an unique alpha-numeric string
392 */
393function nextUid() {
394 return ++uid;
395}
396
397
398/**
399 * Set or clear the hashkey for an object.
400 * @param obj object
401 * @param h the hashkey (!truthy to delete the hashkey)
402 */
403function setHashKey(obj, h) {
404 if (h) {
405 obj.$$hashKey = h;
406 } else {
407 delete obj.$$hashKey;
408 }
409}
410
411
412function baseExtend(dst, objs, deep) {
413 var h = dst.$$hashKey;
414
415 for (var i = 0, ii = objs.length; i < ii; ++i) {
416 var obj = objs[i];
417 if (!isObject(obj) && !isFunction(obj)) continue;
418 var keys = Object.keys(obj);
419 for (var j = 0, jj = keys.length; j < jj; j++) {
420 var key = keys[j];
421 var src = obj[key];
422
423 if (deep && isObject(src)) {
424 if (isDate(src)) {
425 dst[key] = new Date(src.valueOf());
426 } else if (isRegExp(src)) {
427 dst[key] = new RegExp(src);
428 } else {
429 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
430 baseExtend(dst[key], [src], true);
431 }
432 } else {
433 dst[key] = src;
434 }
435 }
436 }
437
438 setHashKey(dst, h);
439 return dst;
440}
441
442/**
443 * @ngdoc function
444 * @name angular.extend
445 * @module ng
446 * @kind function
447 *
448 * @description
449 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
450 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
451 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
452 *
453 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
454 * {@link angular.merge} for this.
455 *
456 * @param {Object} dst Destination object.
457 * @param {...Object} src Source object(s).
458 * @returns {Object} Reference to `dst`.
459 */
460function extend(dst) {
461 return baseExtend(dst, slice.call(arguments, 1), false);
462}
463
464
465/**
466* @ngdoc function
467* @name angular.merge
468* @module ng
469* @kind function
470*
471* @description
472* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
473* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
474* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
475*
476* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
477* objects, performing a deep copy.
478*
479* @param {Object} dst Destination object.
480* @param {...Object} src Source object(s).
481* @returns {Object} Reference to `dst`.
482*/
483function merge(dst) {
484 return baseExtend(dst, slice.call(arguments, 1), true);
485}
486
487
488
489function toInt(str) {
490 return parseInt(str, 10);
491}
492
493
494function inherit(parent, extra) {
495 return extend(Object.create(parent), extra);
496}
497
498/**
499 * @ngdoc function
500 * @name angular.noop
501 * @module ng
502 * @kind function
503 *
504 * @description
505 * A function that performs no operations. This function can be useful when writing code in the
506 * functional style.
507 ```js
508 function foo(callback) {
509 var result = calculateResult();
510 (callback || angular.noop)(result);
511 }
512 ```
513 */
514function noop() {}
515noop.$inject = [];
516
517
518/**
519 * @ngdoc function
520 * @name angular.identity
521 * @module ng
522 * @kind function
523 *
524 * @description
525 * A function that returns its first argument. This function is useful when writing code in the
526 * functional style.
527 *
528 ```js
529 function transformer(transformationFn, value) {
530 return (transformationFn || angular.identity)(value);
531 };
532 ```
533 * @param {*} value to be returned.
534 * @returns {*} the value passed in.
535 */
536function identity($) {return $;}
537identity.$inject = [];
538
539
540function valueFn(value) {return function() {return value;};}
541
542function hasCustomToString(obj) {
543 return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
544}
545
546
547/**
548 * @ngdoc function
549 * @name angular.isUndefined
550 * @module ng
551 * @kind function
552 *
553 * @description
554 * Determines if a reference is undefined.
555 *
556 * @param {*} value Reference to check.
557 * @returns {boolean} True if `value` is undefined.
558 */
559function isUndefined(value) {return typeof value === 'undefined';}
560
561
562/**
563 * @ngdoc function
564 * @name angular.isDefined
565 * @module ng
566 * @kind function
567 *
568 * @description
569 * Determines if a reference is defined.
570 *
571 * @param {*} value Reference to check.
572 * @returns {boolean} True if `value` is defined.
573 */
574function isDefined(value) {return typeof value !== 'undefined';}
575
576
577/**
578 * @ngdoc function
579 * @name angular.isObject
580 * @module ng
581 * @kind function
582 *
583 * @description
584 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
585 * considered to be objects. Note that JavaScript arrays are objects.
586 *
587 * @param {*} value Reference to check.
588 * @returns {boolean} True if `value` is an `Object` but not `null`.
589 */
590function isObject(value) {
591 // http://jsperf.com/isobject4
592 return value !== null && typeof value === 'object';
593}
594
595
596/**
597 * Determine if a value is an object with a null prototype
598 *
599 * @returns {boolean} True if `value` is an `Object` with a null prototype
600 */
601function isBlankObject(value) {
602 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
603}
604
605
606/**
607 * @ngdoc function
608 * @name angular.isString
609 * @module ng
610 * @kind function
611 *
612 * @description
613 * Determines if a reference is a `String`.
614 *
615 * @param {*} value Reference to check.
616 * @returns {boolean} True if `value` is a `String`.
617 */
618function isString(value) {return typeof value === 'string';}
619
620
621/**
622 * @ngdoc function
623 * @name angular.isNumber
624 * @module ng
625 * @kind function
626 *
627 * @description
628 * Determines if a reference is a `Number`.
629 *
630 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
631 *
632 * If you wish to exclude these then you can use the native
633 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
634 * method.
635 *
636 * @param {*} value Reference to check.
637 * @returns {boolean} True if `value` is a `Number`.
638 */
639function isNumber(value) {return typeof value === 'number';}
640
641
642/**
643 * @ngdoc function
644 * @name angular.isDate
645 * @module ng
646 * @kind function
647 *
648 * @description
649 * Determines if a value is a date.
650 *
651 * @param {*} value Reference to check.
652 * @returns {boolean} True if `value` is a `Date`.
653 */
654function isDate(value) {
655 return toString.call(value) === '[object Date]';
656}
657
658
659/**
660 * @ngdoc function
661 * @name angular.isArray
662 * @module ng
663 * @kind function
664 *
665 * @description
666 * Determines if a reference is an `Array`.
667 *
668 * @param {*} value Reference to check.
669 * @returns {boolean} True if `value` is an `Array`.
670 */
671var isArray = Array.isArray;
672
673/**
674 * @ngdoc function
675 * @name angular.isFunction
676 * @module ng
677 * @kind function
678 *
679 * @description
680 * Determines if a reference is a `Function`.
681 *
682 * @param {*} value Reference to check.
683 * @returns {boolean} True if `value` is a `Function`.
684 */
685function isFunction(value) {return typeof value === 'function';}
686
687
688/**
689 * Determines if a value is a regular expression object.
690 *
691 * @private
692 * @param {*} value Reference to check.
693 * @returns {boolean} True if `value` is a `RegExp`.
694 */
695function isRegExp(value) {
696 return toString.call(value) === '[object RegExp]';
697}
698
699
700/**
701 * Checks if `obj` is a window object.
702 *
703 * @private
704 * @param {*} obj Object to check
705 * @returns {boolean} True if `obj` is a window obj.
706 */
707function isWindow(obj) {
708 return obj && obj.window === obj;
709}
710
711
712function isScope(obj) {
713 return obj && obj.$evalAsync && obj.$watch;
714}
715
716
717function isFile(obj) {
718 return toString.call(obj) === '[object File]';
719}
720
721
722function isFormData(obj) {
723 return toString.call(obj) === '[object FormData]';
724}
725
726
727function isBlob(obj) {
728 return toString.call(obj) === '[object Blob]';
729}
730
731
732function isBoolean(value) {
733 return typeof value === 'boolean';
734}
735
736
737function isPromiseLike(obj) {
738 return obj && isFunction(obj.then);
739}
740
741
742var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
743function isTypedArray(value) {
744 return TYPED_ARRAY_REGEXP.test(toString.call(value));
745}
746
747
748var trim = function(value) {
749 return isString(value) ? value.trim() : value;
750};
751
752// Copied from:
753// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
754// Prereq: s is a string.
755var escapeForRegexp = function(s) {
756 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
757 replace(/\x08/g, '\\x08');
758};
759
760
761/**
762 * @ngdoc function
763 * @name angular.isElement
764 * @module ng
765 * @kind function
766 *
767 * @description
768 * Determines if a reference is a DOM element (or wrapped jQuery element).
769 *
770 * @param {*} value Reference to check.
771 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
772 */
773function isElement(node) {
774 return !!(node &&
775 (node.nodeName // we are a direct element
776 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
777}
778
779/**
780 * @param str 'key1,key2,...'
781 * @returns {object} in the form of {key1:true, key2:true, ...}
782 */
783function makeMap(str) {
784 var obj = {}, items = str.split(","), i;
785 for (i = 0; i < items.length; i++) {
786 obj[items[i]] = true;
787 }
788 return obj;
789}
790
791
792function nodeName_(element) {
793 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
794}
795
796function includes(array, obj) {
797 return Array.prototype.indexOf.call(array, obj) != -1;
798}
799
800function arrayRemove(array, value) {
801 var index = array.indexOf(value);
802 if (index >= 0) {
803 array.splice(index, 1);
804 }
805 return index;
806}
807
808/**
809 * @ngdoc function
810 * @name angular.copy
811 * @module ng
812 * @kind function
813 *
814 * @description
815 * Creates a deep copy of `source`, which should be an object or an array.
816 *
817 * * If no destination is supplied, a copy of the object or array is created.
818 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
819 * are deleted and then all elements/properties from the source are copied to it.
820 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
821 * * If `source` is identical to 'destination' an exception will be thrown.
822 *
823 * @param {*} source The source that will be used to make a copy.
824 * Can be any type, including primitives, `null`, and `undefined`.
825 * @param {(Object|Array)=} destination Destination into which the source is copied. If
826 * provided, must be of the same type as `source`.
827 * @returns {*} The copy or updated `destination`, if `destination` was specified.
828 *
829 * @example
830 <example module="copyExample">
831 <file name="index.html">
832 <div ng-controller="ExampleController">
833 <form novalidate class="simple-form">
834 Name: <input type="text" ng-model="user.name" /><br />
835 E-mail: <input type="email" ng-model="user.email" /><br />
836 Gender: <input type="radio" ng-model="user.gender" value="male" />male
837 <input type="radio" ng-model="user.gender" value="female" />female<br />
838 <button ng-click="reset()">RESET</button>
839 <button ng-click="update(user)">SAVE</button>
840 </form>
841 <pre>form = {{user | json}}</pre>
842 <pre>master = {{master | json}}</pre>
843 </div>
844
845 <script>
846 angular.module('copyExample', [])
847 .controller('ExampleController', ['$scope', function($scope) {
848 $scope.master= {};
849
850 $scope.update = function(user) {
851 // Example with 1 argument
852 $scope.master= angular.copy(user);
853 };
854
855 $scope.reset = function() {
856 // Example with 2 arguments
857 angular.copy($scope.master, $scope.user);
858 };
859
860 $scope.reset();
861 }]);
862 </script>
863 </file>
864 </example>
865 */
866function copy(source, destination, stackSource, stackDest) {
867 if (isWindow(source) || isScope(source)) {
868 throw ngMinErr('cpws',
869 "Can't copy! Making copies of Window or Scope instances is not supported.");
870 }
871 if (isTypedArray(destination)) {
872 throw ngMinErr('cpta',
873 "Can't copy! TypedArray destination cannot be mutated.");
874 }
875
876 if (!destination) {
877 destination = source;
878 if (isObject(source)) {
879 var index;
880 if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
881 return stackDest[index];
882 }
883
884 // TypedArray, Date and RegExp have specific copy functionality and must be
885 // pushed onto the stack before returning.
886 // Array and other objects create the base object and recurse to copy child
887 // objects. The array/object will be pushed onto the stack when recursed.
888 if (isArray(source)) {
889 return copy(source, [], stackSource, stackDest);
890 } else if (isTypedArray(source)) {
891 destination = new source.constructor(source);
892 } else if (isDate(source)) {
893 destination = new Date(source.getTime());
894 } else if (isRegExp(source)) {
895 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
896 destination.lastIndex = source.lastIndex;
897 } else if (isFunction(source.cloneNode)) {
898 destination = source.cloneNode(true);
899 } else {
900 var emptyObject = Object.create(getPrototypeOf(source));
901 return copy(source, emptyObject, stackSource, stackDest);
902 }
903
904 if (stackDest) {
905 stackSource.push(source);
906 stackDest.push(destination);
907 }
908 }
909 } else {
910 if (source === destination) throw ngMinErr('cpi',
911 "Can't copy! Source and destination are identical.");
912
913 stackSource = stackSource || [];
914 stackDest = stackDest || [];
915
916 if (isObject(source)) {
917 stackSource.push(source);
918 stackDest.push(destination);
919 }
920
921 var result, key;
922 if (isArray(source)) {
923 destination.length = 0;
924 for (var i = 0; i < source.length; i++) {
925 destination.push(copy(source[i], null, stackSource, stackDest));
926 }
927 } else {
928 var h = destination.$$hashKey;
929 if (isArray(destination)) {
930 destination.length = 0;
931 } else {
932 forEach(destination, function(value, key) {
933 delete destination[key];
934 });
935 }
936 if (isBlankObject(source)) {
937 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
938 for (key in source) {
939 destination[key] = copy(source[key], null, stackSource, stackDest);
940 }
941 } else if (source && typeof source.hasOwnProperty === 'function') {
942 // Slow path, which must rely on hasOwnProperty
943 for (key in source) {
944 if (source.hasOwnProperty(key)) {
945 destination[key] = copy(source[key], null, stackSource, stackDest);
946 }
947 }
948 } else {
949 // Slowest path --- hasOwnProperty can't be called as a method
950 for (key in source) {
951 if (hasOwnProperty.call(source, key)) {
952 destination[key] = copy(source[key], null, stackSource, stackDest);
953 }
954 }
955 }
956 setHashKey(destination,h);
957 }
958 }
959 return destination;
960}
961
962/**
963 * Creates a shallow copy of an object, an array or a primitive.
964 *
965 * Assumes that there are no proto properties for objects.
966 */
967function shallowCopy(src, dst) {
968 if (isArray(src)) {
969 dst = dst || [];
970
971 for (var i = 0, ii = src.length; i < ii; i++) {
972 dst[i] = src[i];
973 }
974 } else if (isObject(src)) {
975 dst = dst || {};
976
977 for (var key in src) {
978 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
979 dst[key] = src[key];
980 }
981 }
982 }
983
984 return dst || src;
985}
986
987
988/**
989 * @ngdoc function
990 * @name angular.equals
991 * @module ng
992 * @kind function
993 *
994 * @description
995 * Determines if two objects or two values are equivalent. Supports value types, regular
996 * expressions, arrays and objects.
997 *
998 * Two objects or values are considered equivalent if at least one of the following is true:
999 *
1000 * * Both objects or values pass `===` comparison.
1001 * * Both objects or values are of the same type and all of their properties are equal by
1002 * comparing them with `angular.equals`.
1003 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1004 * * Both values represent the same regular expression (In JavaScript,
1005 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1006 * representation matches).
1007 *
1008 * During a property comparison, properties of `function` type and properties with names
1009 * that begin with `$` are ignored.
1010 *
1011 * Scope and DOMWindow objects are being compared only by identify (`===`).
1012 *
1013 * @param {*} o1 Object or value to compare.
1014 * @param {*} o2 Object or value to compare.
1015 * @returns {boolean} True if arguments are equal.
1016 */
1017function equals(o1, o2) {
1018 if (o1 === o2) return true;
1019 if (o1 === null || o2 === null) return false;
1020 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1021 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1022 if (t1 == t2) {
1023 if (t1 == 'object') {
1024 if (isArray(o1)) {
1025 if (!isArray(o2)) return false;
1026 if ((length = o1.length) == o2.length) {
1027 for (key = 0; key < length; key++) {
1028 if (!equals(o1[key], o2[key])) return false;
1029 }
1030 return true;
1031 }
1032 } else if (isDate(o1)) {
1033 if (!isDate(o2)) return false;
1034 return equals(o1.getTime(), o2.getTime());
1035 } else if (isRegExp(o1)) {
1036 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1037 } else {
1038 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1039 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1040 keySet = createMap();
1041 for (key in o1) {
1042 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1043 if (!equals(o1[key], o2[key])) return false;
1044 keySet[key] = true;
1045 }
1046 for (key in o2) {
1047 if (!(key in keySet) &&
1048 key.charAt(0) !== '$' &&
1049 isDefined(o2[key]) &&
1050 !isFunction(o2[key])) return false;
1051 }
1052 return true;
1053 }
1054 }
1055 }
1056 return false;
1057}
1058
1059var csp = function() {
1060 if (!isDefined(csp.rules)) {
1061
1062
1063 var ngCspElement = (document.querySelector('[ng-csp]') ||
1064 document.querySelector('[data-ng-csp]'));
1065
1066 if (ngCspElement) {
1067 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1068 ngCspElement.getAttribute('data-ng-csp');
1069 csp.rules = {
1070 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1071 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1072 };
1073 } else {
1074 csp.rules = {
1075 noUnsafeEval: noUnsafeEval(),
1076 noInlineStyle: false
1077 };
1078 }
1079 }
1080
1081 return csp.rules;
1082
1083 function noUnsafeEval() {
1084 try {
1085 /* jshint -W031, -W054 */
1086 new Function('');
1087 /* jshint +W031, +W054 */
1088 return false;
1089 } catch (e) {
1090 return true;
1091 }
1092 }
1093};
1094
1095/**
1096 * @ngdoc directive
1097 * @module ng
1098 * @name ngJq
1099 *
1100 * @element ANY
1101 * @param {string=} ngJq the name of the library available under `window`
1102 * to be used for angular.element
1103 * @description
1104 * Use this directive to force the angular.element library. This should be
1105 * used to force either jqLite by leaving ng-jq blank or setting the name of
1106 * the jquery variable under window (eg. jQuery).
1107 *
1108 * Since angular looks for this directive when it is loaded (doesn't wait for the
1109 * DOMContentLoaded event), it must be placed on an element that comes before the script
1110 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1111 * others ignored.
1112 *
1113 * @example
1114 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1115 ```html
1116 <!doctype html>
1117 <html ng-app ng-jq>
1118 ...
1119 ...
1120 </html>
1121 ```
1122 * @example
1123 * This example shows how to use a jQuery based library of a different name.
1124 * The library name must be available at the top most 'window'.
1125 ```html
1126 <!doctype html>
1127 <html ng-app ng-jq="jQueryLib">
1128 ...
1129 ...
1130 </html>
1131 ```
1132 */
1133var jq = function() {
1134 if (isDefined(jq.name_)) return jq.name_;
1135 var el;
1136 var i, ii = ngAttrPrefixes.length, prefix, name;
1137 for (i = 0; i < ii; ++i) {
1138 prefix = ngAttrPrefixes[i];
1139 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1140 name = el.getAttribute(prefix + 'jq');
1141 break;
1142 }
1143 }
1144
1145 return (jq.name_ = name);
1146};
1147
1148function concat(array1, array2, index) {
1149 return array1.concat(slice.call(array2, index));
1150}
1151
1152function sliceArgs(args, startIndex) {
1153 return slice.call(args, startIndex || 0);
1154}
1155
1156
1157/* jshint -W101 */
1158/**
1159 * @ngdoc function
1160 * @name angular.bind
1161 * @module ng
1162 * @kind function
1163 *
1164 * @description
1165 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1166 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1167 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1168 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1169 *
1170 * @param {Object} self Context which `fn` should be evaluated in.
1171 * @param {function()} fn Function to be bound.
1172 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1173 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1174 */
1175/* jshint +W101 */
1176function bind(self, fn) {
1177 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1178 if (isFunction(fn) && !(fn instanceof RegExp)) {
1179 return curryArgs.length
1180 ? function() {
1181 return arguments.length
1182 ? fn.apply(self, concat(curryArgs, arguments, 0))
1183 : fn.apply(self, curryArgs);
1184 }
1185 : function() {
1186 return arguments.length
1187 ? fn.apply(self, arguments)
1188 : fn.call(self);
1189 };
1190 } else {
1191 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1192 return fn;
1193 }
1194}
1195
1196
1197function toJsonReplacer(key, value) {
1198 var val = value;
1199
1200 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1201 val = undefined;
1202 } else if (isWindow(value)) {
1203 val = '$WINDOW';
1204 } else if (value && document === value) {
1205 val = '$DOCUMENT';
1206 } else if (isScope(value)) {
1207 val = '$SCOPE';
1208 }
1209
1210 return val;
1211}
1212
1213
1214/**
1215 * @ngdoc function
1216 * @name angular.toJson
1217 * @module ng
1218 * @kind function
1219 *
1220 * @description
1221 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1222 * stripped since angular uses this notation internally.
1223 *
1224 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1225 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1226 * If set to an integer, the JSON output will contain that many spaces per indentation.
1227 * @returns {string|undefined} JSON-ified string representing `obj`.
1228 */
1229function toJson(obj, pretty) {
1230 if (typeof obj === 'undefined') return undefined;
1231 if (!isNumber(pretty)) {
1232 pretty = pretty ? 2 : null;
1233 }
1234 return JSON.stringify(obj, toJsonReplacer, pretty);
1235}
1236
1237
1238/**
1239 * @ngdoc function
1240 * @name angular.fromJson
1241 * @module ng
1242 * @kind function
1243 *
1244 * @description
1245 * Deserializes a JSON string.
1246 *
1247 * @param {string} json JSON string to deserialize.
1248 * @returns {Object|Array|string|number} Deserialized JSON string.
1249 */
1250function fromJson(json) {
1251 return isString(json)
1252 ? JSON.parse(json)
1253 : json;
1254}
1255
1256
1257function timezoneToOffset(timezone, fallback) {
1258 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1259 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1260}
1261
1262
1263function addDateMinutes(date, minutes) {
1264 date = new Date(date.getTime());
1265 date.setMinutes(date.getMinutes() + minutes);
1266 return date;
1267}
1268
1269
1270function convertTimezoneToLocal(date, timezone, reverse) {
1271 reverse = reverse ? -1 : 1;
1272 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1273 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1274}
1275
1276
1277/**
1278 * @returns {string} Returns the string representation of the element.
1279 */
1280function startingTag(element) {
1281 element = jqLite(element).clone();
1282 try {
1283 // turns out IE does not let you set .html() on elements which
1284 // are not allowed to have children. So we just ignore it.
1285 element.empty();
1286 } catch (e) {}
1287 var elemHtml = jqLite('<div>').append(element).html();
1288 try {
1289 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1290 elemHtml.
1291 match(/^(<[^>]+>)/)[1].
1292 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1293 } catch (e) {
1294 return lowercase(elemHtml);
1295 }
1296
1297}
1298
1299
1300/////////////////////////////////////////////////
1301
1302/**
1303 * Tries to decode the URI component without throwing an exception.
1304 *
1305 * @private
1306 * @param str value potential URI component to check.
1307 * @returns {boolean} True if `value` can be decoded
1308 * with the decodeURIComponent function.
1309 */
1310function tryDecodeURIComponent(value) {
1311 try {
1312 return decodeURIComponent(value);
1313 } catch (e) {
1314 // Ignore any invalid uri component
1315 }
1316}
1317
1318
1319/**
1320 * Parses an escaped url query string into key-value pairs.
1321 * @returns {Object.<string,boolean|Array>}
1322 */
1323function parseKeyValue(/**string*/keyValue) {
1324 var obj = {};
1325 forEach((keyValue || "").split('&'), function(keyValue) {
1326 var splitPoint, key, val;
1327 if (keyValue) {
1328 key = keyValue = keyValue.replace(/\+/g,'%20');
1329 splitPoint = keyValue.indexOf('=');
1330 if (splitPoint !== -1) {
1331 key = keyValue.substring(0, splitPoint);
1332 val = keyValue.substring(splitPoint + 1);
1333 }
1334 key = tryDecodeURIComponent(key);
1335 if (isDefined(key)) {
1336 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1337 if (!hasOwnProperty.call(obj, key)) {
1338 obj[key] = val;
1339 } else if (isArray(obj[key])) {
1340 obj[key].push(val);
1341 } else {
1342 obj[key] = [obj[key],val];
1343 }
1344 }
1345 }
1346 });
1347 return obj;
1348}
1349
1350function toKeyValue(obj) {
1351 var parts = [];
1352 forEach(obj, function(value, key) {
1353 if (isArray(value)) {
1354 forEach(value, function(arrayValue) {
1355 parts.push(encodeUriQuery(key, true) +
1356 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1357 });
1358 } else {
1359 parts.push(encodeUriQuery(key, true) +
1360 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1361 }
1362 });
1363 return parts.length ? parts.join('&') : '';
1364}
1365
1366
1367/**
1368 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1369 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1370 * segments:
1371 * segment = *pchar
1372 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1373 * pct-encoded = "%" HEXDIG HEXDIG
1374 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1375 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1376 * / "*" / "+" / "," / ";" / "="
1377 */
1378function encodeUriSegment(val) {
1379 return encodeUriQuery(val, true).
1380 replace(/%26/gi, '&').
1381 replace(/%3D/gi, '=').
1382 replace(/%2B/gi, '+');
1383}
1384
1385
1386/**
1387 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1388 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1389 * encoded per http://tools.ietf.org/html/rfc3986:
1390 * query = *( pchar / "/" / "?" )
1391 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1392 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1393 * pct-encoded = "%" HEXDIG HEXDIG
1394 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1395 * / "*" / "+" / "," / ";" / "="
1396 */
1397function encodeUriQuery(val, pctEncodeSpaces) {
1398 return encodeURIComponent(val).
1399 replace(/%40/gi, '@').
1400 replace(/%3A/gi, ':').
1401 replace(/%24/g, '$').
1402 replace(/%2C/gi, ',').
1403 replace(/%3B/gi, ';').
1404 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1405}
1406
1407var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1408
1409function getNgAttribute(element, ngAttr) {
1410 var attr, i, ii = ngAttrPrefixes.length;
1411 for (i = 0; i < ii; ++i) {
1412 attr = ngAttrPrefixes[i] + ngAttr;
1413 if (isString(attr = element.getAttribute(attr))) {
1414 return attr;
1415 }
1416 }
1417 return null;
1418}
1419
1420/**
1421 * @ngdoc directive
1422 * @name ngApp
1423 * @module ng
1424 *
1425 * @element ANY
1426 * @param {angular.Module} ngApp an optional application
1427 * {@link angular.module module} name to load.
1428 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1429 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1430 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1431 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1432 * tracking down the root of these bugs.
1433 *
1434 * @description
1435 *
1436 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1437 * designates the **root element** of the application and is typically placed near the root element
1438 * of the page - e.g. on the `<body>` or `<html>` tags.
1439 *
1440 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1441 * found in the document will be used to define the root element to auto-bootstrap as an
1442 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1443 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1444 *
1445 * You can specify an **AngularJS module** to be used as the root module for the application. This
1446 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1447 * should contain the application code needed or have dependencies on other modules that will
1448 * contain the code. See {@link angular.module} for more information.
1449 *
1450 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1451 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1452 * would not be resolved to `3`.
1453 *
1454 * `ngApp` is the easiest, and most common way to bootstrap an application.
1455 *
1456 <example module="ngAppDemo">
1457 <file name="index.html">
1458 <div ng-controller="ngAppDemoController">
1459 I can add: {{a}} + {{b}} = {{ a+b }}
1460 </div>
1461 </file>
1462 <file name="script.js">
1463 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1464 $scope.a = 1;
1465 $scope.b = 2;
1466 });
1467 </file>
1468 </example>
1469 *
1470 * Using `ngStrictDi`, you would see something like this:
1471 *
1472 <example ng-app-included="true">
1473 <file name="index.html">
1474 <div ng-app="ngAppStrictDemo" ng-strict-di>
1475 <div ng-controller="GoodController1">
1476 I can add: {{a}} + {{b}} = {{ a+b }}
1477
1478 <p>This renders because the controller does not fail to
1479 instantiate, by using explicit annotation style (see
1480 script.js for details)
1481 </p>
1482 </div>
1483
1484 <div ng-controller="GoodController2">
1485 Name: <input ng-model="name"><br />
1486 Hello, {{name}}!
1487
1488 <p>This renders because the controller does not fail to
1489 instantiate, by using explicit annotation style
1490 (see script.js for details)
1491 </p>
1492 </div>
1493
1494 <div ng-controller="BadController">
1495 I can add: {{a}} + {{b}} = {{ a+b }}
1496
1497 <p>The controller could not be instantiated, due to relying
1498 on automatic function annotations (which are disabled in
1499 strict mode). As such, the content of this section is not
1500 interpolated, and there should be an error in your web console.
1501 </p>
1502 </div>
1503 </div>
1504 </file>
1505 <file name="script.js">
1506 angular.module('ngAppStrictDemo', [])
1507 // BadController will fail to instantiate, due to relying on automatic function annotation,
1508 // rather than an explicit annotation
1509 .controller('BadController', function($scope) {
1510 $scope.a = 1;
1511 $scope.b = 2;
1512 })
1513 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1514 // due to using explicit annotations using the array style and $inject property, respectively.
1515 .controller('GoodController1', ['$scope', function($scope) {
1516 $scope.a = 1;
1517 $scope.b = 2;
1518 }])
1519 .controller('GoodController2', GoodController2);
1520 function GoodController2($scope) {
1521 $scope.name = "World";
1522 }
1523 GoodController2.$inject = ['$scope'];
1524 </file>
1525 <file name="style.css">
1526 div[ng-controller] {
1527 margin-bottom: 1em;
1528 -webkit-border-radius: 4px;
1529 border-radius: 4px;
1530 border: 1px solid;
1531 padding: .5em;
1532 }
1533 div[ng-controller^=Good] {
1534 border-color: #d6e9c6;
1535 background-color: #dff0d8;
1536 color: #3c763d;
1537 }
1538 div[ng-controller^=Bad] {
1539 border-color: #ebccd1;
1540 background-color: #f2dede;
1541 color: #a94442;
1542 margin-bottom: 0;
1543 }
1544 </file>
1545 </example>
1546 */
1547function angularInit(element, bootstrap) {
1548 var appElement,
1549 module,
1550 config = {};
1551
1552 // The element `element` has priority over any other element
1553 forEach(ngAttrPrefixes, function(prefix) {
1554 var name = prefix + 'app';
1555
1556 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1557 appElement = element;
1558 module = element.getAttribute(name);
1559 }
1560 });
1561 forEach(ngAttrPrefixes, function(prefix) {
1562 var name = prefix + 'app';
1563 var candidate;
1564
1565 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1566 appElement = candidate;
1567 module = candidate.getAttribute(name);
1568 }
1569 });
1570 if (appElement) {
1571 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1572 bootstrap(appElement, module ? [module] : [], config);
1573 }
1574}
1575
1576/**
1577 * @ngdoc function
1578 * @name angular.bootstrap
1579 * @module ng
1580 * @description
1581 * Use this function to manually start up angular application.
1582 *
1583 * See: {@link guide/bootstrap Bootstrap}
1584 *
1585 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1586 * They must use {@link ng.directive:ngApp ngApp}.
1587 *
1588 * Angular will detect if it has been loaded into the browser more than once and only allow the
1589 * first loaded script to be bootstrapped and will report a warning to the browser console for
1590 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1591 * multiple instances of Angular try to work on the DOM.
1592 *
1593 * ```html
1594 * <!doctype html>
1595 * <html>
1596 * <body>
1597 * <div ng-controller="WelcomeController">
1598 * {{greeting}}
1599 * </div>
1600 *
1601 * <script src="angular.js"></script>
1602 * <script>
1603 * var app = angular.module('demo', [])
1604 * .controller('WelcomeController', function($scope) {
1605 * $scope.greeting = 'Welcome!';
1606 * });
1607 * angular.bootstrap(document, ['demo']);
1608 * </script>
1609 * </body>
1610 * </html>
1611 * ```
1612 *
1613 * @param {DOMElement} element DOM element which is the root of angular application.
1614 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1615 * Each item in the array should be the name of a predefined module or a (DI annotated)
1616 * function that will be invoked by the injector as a `config` block.
1617 * See: {@link angular.module modules}
1618 * @param {Object=} config an object for defining configuration options for the application. The
1619 * following keys are supported:
1620 *
1621 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1622 * assist in finding bugs which break minified code. Defaults to `false`.
1623 *
1624 * @returns {auto.$injector} Returns the newly created injector for this app.
1625 */
1626function bootstrap(element, modules, config) {
1627 if (!isObject(config)) config = {};
1628 var defaultConfig = {
1629 strictDi: false
1630 };
1631 config = extend(defaultConfig, config);
1632 var doBootstrap = function() {
1633 element = jqLite(element);
1634
1635 if (element.injector()) {
1636 var tag = (element[0] === document) ? 'document' : startingTag(element);
1637 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1638 throw ngMinErr(
1639 'btstrpd',
1640 "App Already Bootstrapped with this Element '{0}'",
1641 tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
1642 }
1643
1644 modules = modules || [];
1645 modules.unshift(['$provide', function($provide) {
1646 $provide.value('$rootElement', element);
1647 }]);
1648
1649 if (config.debugInfoEnabled) {
1650 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1651 modules.push(['$compileProvider', function($compileProvider) {
1652 $compileProvider.debugInfoEnabled(true);
1653 }]);
1654 }
1655
1656 modules.unshift('ng');
1657 var injector = createInjector(modules, config.strictDi);
1658 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1659 function bootstrapApply(scope, element, compile, injector) {
1660 scope.$apply(function() {
1661 element.data('$injector', injector);
1662 compile(element)(scope);
1663 });
1664 }]
1665 );
1666 return injector;
1667 };
1668
1669 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1670 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1671
1672 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1673 config.debugInfoEnabled = true;
1674 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1675 }
1676
1677 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1678 return doBootstrap();
1679 }
1680
1681 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1682 angular.resumeBootstrap = function(extraModules) {
1683 forEach(extraModules, function(module) {
1684 modules.push(module);
1685 });
1686 return doBootstrap();
1687 };
1688
1689 if (isFunction(angular.resumeDeferredBootstrap)) {
1690 angular.resumeDeferredBootstrap();
1691 }
1692}
1693
1694/**
1695 * @ngdoc function
1696 * @name angular.reloadWithDebugInfo
1697 * @module ng
1698 * @description
1699 * Use this function to reload the current application with debug information turned on.
1700 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1701 *
1702 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1703 */
1704function reloadWithDebugInfo() {
1705 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1706 window.location.reload();
1707}
1708
1709/**
1710 * @name angular.getTestability
1711 * @module ng
1712 * @description
1713 * Get the testability service for the instance of Angular on the given
1714 * element.
1715 * @param {DOMElement} element DOM element which is the root of angular application.
1716 */
1717function getTestability(rootElement) {
1718 var injector = angular.element(rootElement).injector();
1719 if (!injector) {
1720 throw ngMinErr('test',
1721 'no injector found for element argument to getTestability');
1722 }
1723 return injector.get('$$testability');
1724}
1725
1726var SNAKE_CASE_REGEXP = /[A-Z]/g;
1727function snake_case(name, separator) {
1728 separator = separator || '_';
1729 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1730 return (pos ? separator : '') + letter.toLowerCase();
1731 });
1732}
1733
1734var bindJQueryFired = false;
1735var skipDestroyOnNextJQueryCleanData;
1736function bindJQuery() {
1737 var originalCleanData;
1738
1739 if (bindJQueryFired) {
1740 return;
1741 }
1742
1743 // bind to jQuery if present;
1744 var jqName = jq();
1745 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1746 !jqName ? undefined : // use jqLite
1747 window[jqName]; // use jQuery specified by `ngJq`
1748
1749 // Use jQuery if it exists with proper functionality, otherwise default to us.
1750 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1751 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1752 // versions. It will not work for sure with jQuery <1.7, though.
1753 if (jQuery && jQuery.fn.on) {
1754 jqLite = jQuery;
1755 extend(jQuery.fn, {
1756 scope: JQLitePrototype.scope,
1757 isolateScope: JQLitePrototype.isolateScope,
1758 controller: JQLitePrototype.controller,
1759 injector: JQLitePrototype.injector,
1760 inheritedData: JQLitePrototype.inheritedData
1761 });
1762
1763 // All nodes removed from the DOM via various jQuery APIs like .remove()
1764 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1765 // the $destroy event on all removed nodes.
1766 originalCleanData = jQuery.cleanData;
1767 jQuery.cleanData = function(elems) {
1768 var events;
1769 if (!skipDestroyOnNextJQueryCleanData) {
1770 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1771 events = jQuery._data(elem, "events");
1772 if (events && events.$destroy) {
1773 jQuery(elem).triggerHandler('$destroy');
1774 }
1775 }
1776 } else {
1777 skipDestroyOnNextJQueryCleanData = false;
1778 }
1779 originalCleanData(elems);
1780 };
1781 } else {
1782 jqLite = JQLite;
1783 }
1784
1785 angular.element = jqLite;
1786
1787 // Prevent double-proxying.
1788 bindJQueryFired = true;
1789}
1790
1791/**
1792 * throw error if the argument is falsy.
1793 */
1794function assertArg(arg, name, reason) {
1795 if (!arg) {
1796 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1797 }
1798 return arg;
1799}
1800
1801function assertArgFn(arg, name, acceptArrayAnnotation) {
1802 if (acceptArrayAnnotation && isArray(arg)) {
1803 arg = arg[arg.length - 1];
1804 }
1805
1806 assertArg(isFunction(arg), name, 'not a function, got ' +
1807 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1808 return arg;
1809}
1810
1811/**
1812 * throw error if the name given is hasOwnProperty
1813 * @param {String} name the name to test
1814 * @param {String} context the context in which the name is used, such as module or directive
1815 */
1816function assertNotHasOwnProperty(name, context) {
1817 if (name === 'hasOwnProperty') {
1818 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1819 }
1820}
1821
1822/**
1823 * Return the value accessible from the object by path. Any undefined traversals are ignored
1824 * @param {Object} obj starting object
1825 * @param {String} path path to traverse
1826 * @param {boolean} [bindFnToScope=true]
1827 * @returns {Object} value as accessible by path
1828 */
1829//TODO(misko): this function needs to be removed
1830function getter(obj, path, bindFnToScope) {
1831 if (!path) return obj;
1832 var keys = path.split('.');
1833 var key;
1834 var lastInstance = obj;
1835 var len = keys.length;
1836
1837 for (var i = 0; i < len; i++) {
1838 key = keys[i];
1839 if (obj) {
1840 obj = (lastInstance = obj)[key];
1841 }
1842 }
1843 if (!bindFnToScope && isFunction(obj)) {
1844 return bind(lastInstance, obj);
1845 }
1846 return obj;
1847}
1848
1849/**
1850 * Return the DOM siblings between the first and last node in the given array.
1851 * @param {Array} array like object
1852 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1853 */
1854function getBlockNodes(nodes) {
1855 // TODO(perf): update `nodes` instead of creating a new object?
1856 var node = nodes[0];
1857 var endNode = nodes[nodes.length - 1];
1858 var blockNodes;
1859
1860 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1861 if (blockNodes || nodes[i] !== node) {
1862 if (!blockNodes) {
1863 blockNodes = jqLite(slice.call(nodes, 0, i));
1864 }
1865 blockNodes.push(node);
1866 }
1867 }
1868
1869 return blockNodes || nodes;
1870}
1871
1872
1873/**
1874 * Creates a new object without a prototype. This object is useful for lookup without having to
1875 * guard against prototypically inherited properties via hasOwnProperty.
1876 *
1877 * Related micro-benchmarks:
1878 * - http://jsperf.com/object-create2
1879 * - http://jsperf.com/proto-map-lookup/2
1880 * - http://jsperf.com/for-in-vs-object-keys2
1881 *
1882 * @returns {Object}
1883 */
1884function createMap() {
1885 return Object.create(null);
1886}
1887
1888var NODE_TYPE_ELEMENT = 1;
1889var NODE_TYPE_ATTRIBUTE = 2;
1890var NODE_TYPE_TEXT = 3;
1891var NODE_TYPE_COMMENT = 8;
1892var NODE_TYPE_DOCUMENT = 9;
1893var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1894
1895/**
1896 * @ngdoc type
1897 * @name angular.Module
1898 * @module ng
1899 * @description
1900 *
1901 * Interface for configuring angular {@link angular.module modules}.
1902 */
1903
1904function setupModuleLoader(window) {
1905
1906 var $injectorMinErr = minErr('$injector');
1907 var ngMinErr = minErr('ng');
1908
1909 function ensure(obj, name, factory) {
1910 return obj[name] || (obj[name] = factory());
1911 }
1912
1913 var angular = ensure(window, 'angular', Object);
1914
1915 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
1916 angular.$$minErr = angular.$$minErr || minErr;
1917
1918 return ensure(angular, 'module', function() {
1919 /** @type {Object.<string, angular.Module>} */
1920 var modules = {};
1921
1922 /**
1923 * @ngdoc function
1924 * @name angular.module
1925 * @module ng
1926 * @description
1927 *
1928 * The `angular.module` is a global place for creating, registering and retrieving Angular
1929 * modules.
1930 * All modules (angular core or 3rd party) that should be available to an application must be
1931 * registered using this mechanism.
1932 *
1933 * Passing one argument retrieves an existing {@link angular.Module},
1934 * whereas passing more than one argument creates a new {@link angular.Module}
1935 *
1936 *
1937 * # Module
1938 *
1939 * A module is a collection of services, directives, controllers, filters, and configuration information.
1940 * `angular.module` is used to configure the {@link auto.$injector $injector}.
1941 *
1942 * ```js
1943 * // Create a new module
1944 * var myModule = angular.module('myModule', []);
1945 *
1946 * // register a new service
1947 * myModule.value('appName', 'MyCoolApp');
1948 *
1949 * // configure existing services inside initialization blocks.
1950 * myModule.config(['$locationProvider', function($locationProvider) {
1951 * // Configure existing providers
1952 * $locationProvider.hashPrefix('!');
1953 * }]);
1954 * ```
1955 *
1956 * Then you can create an injector and load your modules like this:
1957 *
1958 * ```js
1959 * var injector = angular.injector(['ng', 'myModule'])
1960 * ```
1961 *
1962 * However it's more likely that you'll just use
1963 * {@link ng.directive:ngApp ngApp} or
1964 * {@link angular.bootstrap} to simplify this process for you.
1965 *
1966 * @param {!string} name The name of the module to create or retrieve.
1967 * @param {!Array.<string>=} requires If specified then new module is being created. If
1968 * unspecified then the module is being retrieved for further configuration.
1969 * @param {Function=} configFn Optional configuration function for the module. Same as
1970 * {@link angular.Module#config Module#config()}.
1971 * @returns {module} new module with the {@link angular.Module} api.
1972 */
1973 return function module(name, requires, configFn) {
1974 var assertNotHasOwnProperty = function(name, context) {
1975 if (name === 'hasOwnProperty') {
1976 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
1977 }
1978 };
1979
1980 assertNotHasOwnProperty(name, 'module');
1981 if (requires && modules.hasOwnProperty(name)) {
1982 modules[name] = null;
1983 }
1984 return ensure(modules, name, function() {
1985 if (!requires) {
1986 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
1987 "the module name or forgot to load it. If registering a module ensure that you " +
1988 "specify the dependencies as the second argument.", name);
1989 }
1990
1991 /** @type {!Array.<Array.<*>>} */
1992 var invokeQueue = [];
1993
1994 /** @type {!Array.<Function>} */
1995 var configBlocks = [];
1996
1997 /** @type {!Array.<Function>} */
1998 var runBlocks = [];
1999
2000 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2001
2002 /** @type {angular.Module} */
2003 var moduleInstance = {
2004 // Private state
2005 _invokeQueue: invokeQueue,
2006 _configBlocks: configBlocks,
2007 _runBlocks: runBlocks,
2008
2009 /**
2010 * @ngdoc property
2011 * @name angular.Module#requires
2012 * @module ng
2013 *
2014 * @description
2015 * Holds the list of modules which the injector will load before the current module is
2016 * loaded.
2017 */
2018 requires: requires,
2019
2020 /**
2021 * @ngdoc property
2022 * @name angular.Module#name
2023 * @module ng
2024 *
2025 * @description
2026 * Name of the module.
2027 */
2028 name: name,
2029
2030
2031 /**
2032 * @ngdoc method
2033 * @name angular.Module#provider
2034 * @module ng
2035 * @param {string} name service name
2036 * @param {Function} providerType Construction function for creating new instance of the
2037 * service.
2038 * @description
2039 * See {@link auto.$provide#provider $provide.provider()}.
2040 */
2041 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2042
2043 /**
2044 * @ngdoc method
2045 * @name angular.Module#factory
2046 * @module ng
2047 * @param {string} name service name
2048 * @param {Function} providerFunction Function for creating new instance of the service.
2049 * @description
2050 * See {@link auto.$provide#factory $provide.factory()}.
2051 */
2052 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2053
2054 /**
2055 * @ngdoc method
2056 * @name angular.Module#service
2057 * @module ng
2058 * @param {string} name service name
2059 * @param {Function} constructor A constructor function that will be instantiated.
2060 * @description
2061 * See {@link auto.$provide#service $provide.service()}.
2062 */
2063 service: invokeLaterAndSetModuleName('$provide', 'service'),
2064
2065 /**
2066 * @ngdoc method
2067 * @name angular.Module#value
2068 * @module ng
2069 * @param {string} name service name
2070 * @param {*} object Service instance object.
2071 * @description
2072 * See {@link auto.$provide#value $provide.value()}.
2073 */
2074 value: invokeLater('$provide', 'value'),
2075
2076 /**
2077 * @ngdoc method
2078 * @name angular.Module#constant
2079 * @module ng
2080 * @param {string} name constant name
2081 * @param {*} object Constant value.
2082 * @description
2083 * Because the constant are fixed, they get applied before other provide methods.
2084 * See {@link auto.$provide#constant $provide.constant()}.
2085 */
2086 constant: invokeLater('$provide', 'constant', 'unshift'),
2087
2088 /**
2089 * @ngdoc method
2090 * @name angular.Module#decorator
2091 * @module ng
2092 * @param {string} The name of the service to decorate.
2093 * @param {Function} This function will be invoked when the service needs to be
2094 * instantiated and should return the decorated service instance.
2095 * @description
2096 * See {@link auto.$provide#decorator $provide.decorator()}.
2097 */
2098 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2099
2100 /**
2101 * @ngdoc method
2102 * @name angular.Module#animation
2103 * @module ng
2104 * @param {string} name animation name
2105 * @param {Function} animationFactory Factory function for creating new instance of an
2106 * animation.
2107 * @description
2108 *
2109 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2110 *
2111 *
2112 * Defines an animation hook that can be later used with
2113 * {@link $animate $animate} service and directives that use this service.
2114 *
2115 * ```js
2116 * module.animation('.animation-name', function($inject1, $inject2) {
2117 * return {
2118 * eventName : function(element, done) {
2119 * //code to run the animation
2120 * //once complete, then run done()
2121 * return function cancellationFunction(element) {
2122 * //code to cancel the animation
2123 * }
2124 * }
2125 * }
2126 * })
2127 * ```
2128 *
2129 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2130 * {@link ngAnimate ngAnimate module} for more information.
2131 */
2132 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2133
2134 /**
2135 * @ngdoc method
2136 * @name angular.Module#filter
2137 * @module ng
2138 * @param {string} name Filter name - this must be a valid angular expression identifier
2139 * @param {Function} filterFactory Factory function for creating new instance of filter.
2140 * @description
2141 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2142 *
2143 * <div class="alert alert-warning">
2144 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2145 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2146 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2147 * (`myapp_subsection_filterx`).
2148 * </div>
2149 */
2150 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2151
2152 /**
2153 * @ngdoc method
2154 * @name angular.Module#controller
2155 * @module ng
2156 * @param {string|Object} name Controller name, or an object map of controllers where the
2157 * keys are the names and the values are the constructors.
2158 * @param {Function} constructor Controller constructor function.
2159 * @description
2160 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2161 */
2162 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2163
2164 /**
2165 * @ngdoc method
2166 * @name angular.Module#directive
2167 * @module ng
2168 * @param {string|Object} name Directive name, or an object map of directives where the
2169 * keys are the names and the values are the factories.
2170 * @param {Function} directiveFactory Factory function for creating new instance of
2171 * directives.
2172 * @description
2173 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2174 */
2175 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2176
2177 /**
2178 * @ngdoc method
2179 * @name angular.Module#config
2180 * @module ng
2181 * @param {Function} configFn Execute this function on module load. Useful for service
2182 * configuration.
2183 * @description
2184 * Use this method to register work which needs to be performed on module loading.
2185 * For more about how to configure services, see
2186 * {@link providers#provider-recipe Provider Recipe}.
2187 */
2188 config: config,
2189
2190 /**
2191 * @ngdoc method
2192 * @name angular.Module#run
2193 * @module ng
2194 * @param {Function} initializationFn Execute this function after injector creation.
2195 * Useful for application initialization.
2196 * @description
2197 * Use this method to register work which should be performed when the injector is done
2198 * loading all modules.
2199 */
2200 run: function(block) {
2201 runBlocks.push(block);
2202 return this;
2203 }
2204 };
2205
2206 if (configFn) {
2207 config(configFn);
2208 }
2209
2210 return moduleInstance;
2211
2212 /**
2213 * @param {string} provider
2214 * @param {string} method
2215 * @param {String=} insertMethod
2216 * @returns {angular.Module}
2217 */
2218 function invokeLater(provider, method, insertMethod, queue) {
2219 if (!queue) queue = invokeQueue;
2220 return function() {
2221 queue[insertMethod || 'push']([provider, method, arguments]);
2222 return moduleInstance;
2223 };
2224 }
2225
2226 /**
2227 * @param {string} provider
2228 * @param {string} method
2229 * @returns {angular.Module}
2230 */
2231 function invokeLaterAndSetModuleName(provider, method) {
2232 return function(recipeName, factoryFunction) {
2233 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2234 invokeQueue.push([provider, method, arguments]);
2235 return moduleInstance;
2236 };
2237 }
2238 });
2239 };
2240 });
2241
2242}
2243
2244/* global: toDebugString: true */
2245
2246function serializeObject(obj) {
2247 var seen = [];
2248
2249 return JSON.stringify(obj, function(key, val) {
2250 val = toJsonReplacer(key, val);
2251 if (isObject(val)) {
2252
2253 if (seen.indexOf(val) >= 0) return '...';
2254
2255 seen.push(val);
2256 }
2257 return val;
2258 });
2259}
2260
2261function toDebugString(obj) {
2262 if (typeof obj === 'function') {
2263 return obj.toString().replace(/ \{[\s\S]*$/, '');
2264 } else if (isUndefined(obj)) {
2265 return 'undefined';
2266 } else if (typeof obj !== 'string') {
2267 return serializeObject(obj);
2268 }
2269 return obj;
2270}
2271
2272/* global angularModule: true,
2273 version: true,
2274
2275 $CompileProvider,
2276
2277 htmlAnchorDirective,
2278 inputDirective,
2279 inputDirective,
2280 formDirective,
2281 scriptDirective,
2282 selectDirective,
2283 styleDirective,
2284 optionDirective,
2285 ngBindDirective,
2286 ngBindHtmlDirective,
2287 ngBindTemplateDirective,
2288 ngClassDirective,
2289 ngClassEvenDirective,
2290 ngClassOddDirective,
2291 ngCloakDirective,
2292 ngControllerDirective,
2293 ngFormDirective,
2294 ngHideDirective,
2295 ngIfDirective,
2296 ngIncludeDirective,
2297 ngIncludeFillContentDirective,
2298 ngInitDirective,
2299 ngNonBindableDirective,
2300 ngPluralizeDirective,
2301 ngRepeatDirective,
2302 ngShowDirective,
2303 ngStyleDirective,
2304 ngSwitchDirective,
2305 ngSwitchWhenDirective,
2306 ngSwitchDefaultDirective,
2307 ngOptionsDirective,
2308 ngTranscludeDirective,
2309 ngModelDirective,
2310 ngListDirective,
2311 ngChangeDirective,
2312 patternDirective,
2313 patternDirective,
2314 requiredDirective,
2315 requiredDirective,
2316 minlengthDirective,
2317 minlengthDirective,
2318 maxlengthDirective,
2319 maxlengthDirective,
2320 ngValueDirective,
2321 ngModelOptionsDirective,
2322 ngAttributeAliasDirectives,
2323 ngEventDirectives,
2324
2325 $AnchorScrollProvider,
2326 $AnimateProvider,
2327 $CoreAnimateCssProvider,
2328 $$CoreAnimateQueueProvider,
2329 $$CoreAnimateRunnerProvider,
2330 $BrowserProvider,
2331 $CacheFactoryProvider,
2332 $ControllerProvider,
2333 $DocumentProvider,
2334 $ExceptionHandlerProvider,
2335 $FilterProvider,
2336 $$ForceReflowProvider,
2337 $InterpolateProvider,
2338 $IntervalProvider,
2339 $$HashMapProvider,
2340 $HttpProvider,
2341 $HttpParamSerializerProvider,
2342 $HttpParamSerializerJQLikeProvider,
2343 $HttpBackendProvider,
2344 $xhrFactoryProvider,
2345 $LocationProvider,
2346 $LogProvider,
2347 $ParseProvider,
2348 $RootScopeProvider,
2349 $QProvider,
2350 $$QProvider,
2351 $$SanitizeUriProvider,
2352 $SceProvider,
2353 $SceDelegateProvider,
2354 $SnifferProvider,
2355 $TemplateCacheProvider,
2356 $TemplateRequestProvider,
2357 $$TestabilityProvider,
2358 $TimeoutProvider,
2359 $$RAFProvider,
2360 $WindowProvider,
2361 $$jqLiteProvider,
2362 $$CookieReaderProvider
2363*/
2364
2365
2366/**
2367 * @ngdoc object
2368 * @name angular.version
2369 * @module ng
2370 * @description
2371 * An object that contains information about the current AngularJS version.
2372 *
2373 * This object has the following properties:
2374 *
2375 * - `full` – `{string}` – Full version string, such as "0.9.18".
2376 * - `major` – `{number}` – Major version number, such as "0".
2377 * - `minor` – `{number}` – Minor version number, such as "9".
2378 * - `dot` – `{number}` – Dot version number, such as "18".
2379 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2380 */
2381var version = {
2382 full: '1.4.7', // all of these placeholder strings will be replaced by grunt's
2383 major: 1, // package task
2384 minor: 4,
2385 dot: 7,
2386 codeName: 'dark-luminescence'
2387};
2388
2389
2390function publishExternalAPI(angular) {
2391 extend(angular, {
2392 'bootstrap': bootstrap,
2393 'copy': copy,
2394 'extend': extend,
2395 'merge': merge,
2396 'equals': equals,
2397 'element': jqLite,
2398 'forEach': forEach,
2399 'injector': createInjector,
2400 'noop': noop,
2401 'bind': bind,
2402 'toJson': toJson,
2403 'fromJson': fromJson,
2404 'identity': identity,
2405 'isUndefined': isUndefined,
2406 'isDefined': isDefined,
2407 'isString': isString,
2408 'isFunction': isFunction,
2409 'isObject': isObject,
2410 'isNumber': isNumber,
2411 'isElement': isElement,
2412 'isArray': isArray,
2413 'version': version,
2414 'isDate': isDate,
2415 'lowercase': lowercase,
2416 'uppercase': uppercase,
2417 'callbacks': {counter: 0},
2418 'getTestability': getTestability,
2419 '$$minErr': minErr,
2420 '$$csp': csp,
2421 'reloadWithDebugInfo': reloadWithDebugInfo
2422 });
2423
2424 angularModule = setupModuleLoader(window);
2425
2426 angularModule('ng', ['ngLocale'], ['$provide',
2427 function ngModule($provide) {
2428 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2429 $provide.provider({
2430 $$sanitizeUri: $$SanitizeUriProvider
2431 });
2432 $provide.provider('$compile', $CompileProvider).
2433 directive({
2434 a: htmlAnchorDirective,
2435 input: inputDirective,
2436 textarea: inputDirective,
2437 form: formDirective,
2438 script: scriptDirective,
2439 select: selectDirective,
2440 style: styleDirective,
2441 option: optionDirective,
2442 ngBind: ngBindDirective,
2443 ngBindHtml: ngBindHtmlDirective,
2444 ngBindTemplate: ngBindTemplateDirective,
2445 ngClass: ngClassDirective,
2446 ngClassEven: ngClassEvenDirective,
2447 ngClassOdd: ngClassOddDirective,
2448 ngCloak: ngCloakDirective,
2449 ngController: ngControllerDirective,
2450 ngForm: ngFormDirective,
2451 ngHide: ngHideDirective,
2452 ngIf: ngIfDirective,
2453 ngInclude: ngIncludeDirective,
2454 ngInit: ngInitDirective,
2455 ngNonBindable: ngNonBindableDirective,
2456 ngPluralize: ngPluralizeDirective,
2457 ngRepeat: ngRepeatDirective,
2458 ngShow: ngShowDirective,
2459 ngStyle: ngStyleDirective,
2460 ngSwitch: ngSwitchDirective,
2461 ngSwitchWhen: ngSwitchWhenDirective,
2462 ngSwitchDefault: ngSwitchDefaultDirective,
2463 ngOptions: ngOptionsDirective,
2464 ngTransclude: ngTranscludeDirective,
2465 ngModel: ngModelDirective,
2466 ngList: ngListDirective,
2467 ngChange: ngChangeDirective,
2468 pattern: patternDirective,
2469 ngPattern: patternDirective,
2470 required: requiredDirective,
2471 ngRequired: requiredDirective,
2472 minlength: minlengthDirective,
2473 ngMinlength: minlengthDirective,
2474 maxlength: maxlengthDirective,
2475 ngMaxlength: maxlengthDirective,
2476 ngValue: ngValueDirective,
2477 ngModelOptions: ngModelOptionsDirective
2478 }).
2479 directive({
2480 ngInclude: ngIncludeFillContentDirective
2481 }).
2482 directive(ngAttributeAliasDirectives).
2483 directive(ngEventDirectives);
2484 $provide.provider({
2485 $anchorScroll: $AnchorScrollProvider,
2486 $animate: $AnimateProvider,
2487 $animateCss: $CoreAnimateCssProvider,
2488 $$animateQueue: $$CoreAnimateQueueProvider,
2489 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2490 $browser: $BrowserProvider,
2491 $cacheFactory: $CacheFactoryProvider,
2492 $controller: $ControllerProvider,
2493 $document: $DocumentProvider,
2494 $exceptionHandler: $ExceptionHandlerProvider,
2495 $filter: $FilterProvider,
2496 $$forceReflow: $$ForceReflowProvider,
2497 $interpolate: $InterpolateProvider,
2498 $interval: $IntervalProvider,
2499 $http: $HttpProvider,
2500 $httpParamSerializer: $HttpParamSerializerProvider,
2501 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2502 $httpBackend: $HttpBackendProvider,
2503 $xhrFactory: $xhrFactoryProvider,
2504 $location: $LocationProvider,
2505 $log: $LogProvider,
2506 $parse: $ParseProvider,
2507 $rootScope: $RootScopeProvider,
2508 $q: $QProvider,
2509 $$q: $$QProvider,
2510 $sce: $SceProvider,
2511 $sceDelegate: $SceDelegateProvider,
2512 $sniffer: $SnifferProvider,
2513 $templateCache: $TemplateCacheProvider,
2514 $templateRequest: $TemplateRequestProvider,
2515 $$testability: $$TestabilityProvider,
2516 $timeout: $TimeoutProvider,
2517 $window: $WindowProvider,
2518 $$rAF: $$RAFProvider,
2519 $$jqLite: $$jqLiteProvider,
2520 $$HashMap: $$HashMapProvider,
2521 $$cookieReader: $$CookieReaderProvider
2522 });
2523 }
2524 ]);
2525}
2526
2527/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2528 * Any commits to this file should be reviewed with security in mind. *
2529 * Changes to this file can potentially create security vulnerabilities. *
2530 * An approval from 2 Core members with history of modifying *
2531 * this file is required. *
2532 * *
2533 * Does the change somehow allow for arbitrary javascript to be executed? *
2534 * Or allows for someone to change the prototype of built-in objects? *
2535 * Or gives undesired access to variables likes document or window? *
2536 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2537
2538/* global JQLitePrototype: true,
2539 addEventListenerFn: true,
2540 removeEventListenerFn: true,
2541 BOOLEAN_ATTR: true,
2542 ALIASED_ATTR: true,
2543*/
2544
2545//////////////////////////////////
2546//JQLite
2547//////////////////////////////////
2548
2549/**
2550 * @ngdoc function
2551 * @name angular.element
2552 * @module ng
2553 * @kind function
2554 *
2555 * @description
2556 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2557 *
2558 * If jQuery is available, `angular.element` is an alias for the
2559 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2560 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2561 *
2562 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2563 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2564 * commonly needed functionality with the goal of having a very small footprint.</div>
2565 *
2566 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2567 *
2568 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2569 * jqLite; they are never raw DOM references.</div>
2570 *
2571 * ## Angular's jqLite
2572 * jqLite provides only the following jQuery methods:
2573 *
2574 * - [`addClass()`](http://api.jquery.com/addClass/)
2575 * - [`after()`](http://api.jquery.com/after/)
2576 * - [`append()`](http://api.jquery.com/append/)
2577 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2578 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2579 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2580 * - [`clone()`](http://api.jquery.com/clone/)
2581 * - [`contents()`](http://api.jquery.com/contents/)
2582 * - [`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'.
2583 * - [`data()`](http://api.jquery.com/data/)
2584 * - [`detach()`](http://api.jquery.com/detach/)
2585 * - [`empty()`](http://api.jquery.com/empty/)
2586 * - [`eq()`](http://api.jquery.com/eq/)
2587 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2588 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2589 * - [`html()`](http://api.jquery.com/html/)
2590 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2591 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2592 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2593 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2594 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2595 * - [`prepend()`](http://api.jquery.com/prepend/)
2596 * - [`prop()`](http://api.jquery.com/prop/)
2597 * - [`ready()`](http://api.jquery.com/ready/)
2598 * - [`remove()`](http://api.jquery.com/remove/)
2599 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2600 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2601 * - [`removeData()`](http://api.jquery.com/removeData/)
2602 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2603 * - [`text()`](http://api.jquery.com/text/)
2604 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2605 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2606 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2607 * - [`val()`](http://api.jquery.com/val/)
2608 * - [`wrap()`](http://api.jquery.com/wrap/)
2609 *
2610 * ## jQuery/jqLite Extras
2611 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2612 *
2613 * ### Events
2614 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2615 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2616 * element before it is removed.
2617 *
2618 * ### Methods
2619 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2620 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2621 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2622 * `'ngModel'`).
2623 * - `injector()` - retrieves the injector of the current element or its parent.
2624 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2625 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2626 * be enabled.
2627 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2628 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2629 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2630 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2631 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2632 * parent element is reached.
2633 *
2634 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2635 * @returns {Object} jQuery object.
2636 */
2637
2638JQLite.expando = 'ng339';
2639
2640var jqCache = JQLite.cache = {},
2641 jqId = 1,
2642 addEventListenerFn = function(element, type, fn) {
2643 element.addEventListener(type, fn, false);
2644 },
2645 removeEventListenerFn = function(element, type, fn) {
2646 element.removeEventListener(type, fn, false);
2647 };
2648
2649/*
2650 * !!! This is an undocumented "private" function !!!
2651 */
2652JQLite._data = function(node) {
2653 //jQuery always returns an object on cache miss
2654 return this.cache[node[this.expando]] || {};
2655};
2656
2657function jqNextId() { return ++jqId; }
2658
2659
2660var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2661var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2662var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2663var jqLiteMinErr = minErr('jqLite');
2664
2665/**
2666 * Converts snake_case to camelCase.
2667 * Also there is special case for Moz prefix starting with upper case letter.
2668 * @param name Name to normalize
2669 */
2670function camelCase(name) {
2671 return name.
2672 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2673 return offset ? letter.toUpperCase() : letter;
2674 }).
2675 replace(MOZ_HACK_REGEXP, 'Moz$1');
2676}
2677
2678var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2679var HTML_REGEXP = /<|&#?\w+;/;
2680var TAG_NAME_REGEXP = /<([\w:-]+)/;
2681var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2682
2683var wrapMap = {
2684 'option': [1, '<select multiple="multiple">', '</select>'],
2685
2686 'thead': [1, '<table>', '</table>'],
2687 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2688 'tr': [2, '<table><tbody>', '</tbody></table>'],
2689 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2690 '_default': [0, "", ""]
2691};
2692
2693wrapMap.optgroup = wrapMap.option;
2694wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2695wrapMap.th = wrapMap.td;
2696
2697
2698function jqLiteIsTextNode(html) {
2699 return !HTML_REGEXP.test(html);
2700}
2701
2702function jqLiteAcceptsData(node) {
2703 // The window object can accept data but has no nodeType
2704 // Otherwise we are only interested in elements (1) and documents (9)
2705 var nodeType = node.nodeType;
2706 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2707}
2708
2709function jqLiteHasData(node) {
2710 for (var key in jqCache[node.ng339]) {
2711 return true;
2712 }
2713 return false;
2714}
2715
2716function jqLiteBuildFragment(html, context) {
2717 var tmp, tag, wrap,
2718 fragment = context.createDocumentFragment(),
2719 nodes = [], i;
2720
2721 if (jqLiteIsTextNode(html)) {
2722 // Convert non-html into a text node
2723 nodes.push(context.createTextNode(html));
2724 } else {
2725 // Convert html into DOM nodes
2726 tmp = tmp || fragment.appendChild(context.createElement("div"));
2727 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2728 wrap = wrapMap[tag] || wrapMap._default;
2729 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2730
2731 // Descend through wrappers to the right content
2732 i = wrap[0];
2733 while (i--) {
2734 tmp = tmp.lastChild;
2735 }
2736
2737 nodes = concat(nodes, tmp.childNodes);
2738
2739 tmp = fragment.firstChild;
2740 tmp.textContent = "";
2741 }
2742
2743 // Remove wrapper from fragment
2744 fragment.textContent = "";
2745 fragment.innerHTML = ""; // Clear inner HTML
2746 forEach(nodes, function(node) {
2747 fragment.appendChild(node);
2748 });
2749
2750 return fragment;
2751}
2752
2753function jqLiteParseHTML(html, context) {
2754 context = context || document;
2755 var parsed;
2756
2757 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2758 return [context.createElement(parsed[1])];
2759 }
2760
2761 if ((parsed = jqLiteBuildFragment(html, context))) {
2762 return parsed.childNodes;
2763 }
2764
2765 return [];
2766}
2767
2768/////////////////////////////////////////////
2769function JQLite(element) {
2770 if (element instanceof JQLite) {
2771 return element;
2772 }
2773
2774 var argIsString;
2775
2776 if (isString(element)) {
2777 element = trim(element);
2778 argIsString = true;
2779 }
2780 if (!(this instanceof JQLite)) {
2781 if (argIsString && element.charAt(0) != '<') {
2782 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2783 }
2784 return new JQLite(element);
2785 }
2786
2787 if (argIsString) {
2788 jqLiteAddNodes(this, jqLiteParseHTML(element));
2789 } else {
2790 jqLiteAddNodes(this, element);
2791 }
2792}
2793
2794function jqLiteClone(element) {
2795 return element.cloneNode(true);
2796}
2797
2798function jqLiteDealoc(element, onlyDescendants) {
2799 if (!onlyDescendants) jqLiteRemoveData(element);
2800
2801 if (element.querySelectorAll) {
2802 var descendants = element.querySelectorAll('*');
2803 for (var i = 0, l = descendants.length; i < l; i++) {
2804 jqLiteRemoveData(descendants[i]);
2805 }
2806 }
2807}
2808
2809function jqLiteOff(element, type, fn, unsupported) {
2810 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2811
2812 var expandoStore = jqLiteExpandoStore(element);
2813 var events = expandoStore && expandoStore.events;
2814 var handle = expandoStore && expandoStore.handle;
2815
2816 if (!handle) return; //no listeners registered
2817
2818 if (!type) {
2819 for (type in events) {
2820 if (type !== '$destroy') {
2821 removeEventListenerFn(element, type, handle);
2822 }
2823 delete events[type];
2824 }
2825 } else {
2826 forEach(type.split(' '), function(type) {
2827 if (isDefined(fn)) {
2828 var listenerFns = events[type];
2829 arrayRemove(listenerFns || [], fn);
2830 if (listenerFns && listenerFns.length > 0) {
2831 return;
2832 }
2833 }
2834
2835 removeEventListenerFn(element, type, handle);
2836 delete events[type];
2837 });
2838 }
2839}
2840
2841function jqLiteRemoveData(element, name) {
2842 var expandoId = element.ng339;
2843 var expandoStore = expandoId && jqCache[expandoId];
2844
2845 if (expandoStore) {
2846 if (name) {
2847 delete expandoStore.data[name];
2848 return;
2849 }
2850
2851 if (expandoStore.handle) {
2852 if (expandoStore.events.$destroy) {
2853 expandoStore.handle({}, '$destroy');
2854 }
2855 jqLiteOff(element);
2856 }
2857 delete jqCache[expandoId];
2858 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2859 }
2860}
2861
2862
2863function jqLiteExpandoStore(element, createIfNecessary) {
2864 var expandoId = element.ng339,
2865 expandoStore = expandoId && jqCache[expandoId];
2866
2867 if (createIfNecessary && !expandoStore) {
2868 element.ng339 = expandoId = jqNextId();
2869 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2870 }
2871
2872 return expandoStore;
2873}
2874
2875
2876function jqLiteData(element, key, value) {
2877 if (jqLiteAcceptsData(element)) {
2878
2879 var isSimpleSetter = isDefined(value);
2880 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2881 var massGetter = !key;
2882 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2883 var data = expandoStore && expandoStore.data;
2884
2885 if (isSimpleSetter) { // data('key', value)
2886 data[key] = value;
2887 } else {
2888 if (massGetter) { // data()
2889 return data;
2890 } else {
2891 if (isSimpleGetter) { // data('key')
2892 // don't force creation of expandoStore if it doesn't exist yet
2893 return data && data[key];
2894 } else { // mass-setter: data({key1: val1, key2: val2})
2895 extend(data, key);
2896 }
2897 }
2898 }
2899 }
2900}
2901
2902function jqLiteHasClass(element, selector) {
2903 if (!element.getAttribute) return false;
2904 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
2905 indexOf(" " + selector + " ") > -1);
2906}
2907
2908function jqLiteRemoveClass(element, cssClasses) {
2909 if (cssClasses && element.setAttribute) {
2910 forEach(cssClasses.split(' '), function(cssClass) {
2911 element.setAttribute('class', trim(
2912 (" " + (element.getAttribute('class') || '') + " ")
2913 .replace(/[\n\t]/g, " ")
2914 .replace(" " + trim(cssClass) + " ", " "))
2915 );
2916 });
2917 }
2918}
2919
2920function jqLiteAddClass(element, cssClasses) {
2921 if (cssClasses && element.setAttribute) {
2922 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
2923 .replace(/[\n\t]/g, " ");
2924
2925 forEach(cssClasses.split(' '), function(cssClass) {
2926 cssClass = trim(cssClass);
2927 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
2928 existingClasses += cssClass + ' ';
2929 }
2930 });
2931
2932 element.setAttribute('class', trim(existingClasses));
2933 }
2934}
2935
2936
2937function jqLiteAddNodes(root, elements) {
2938 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
2939
2940 if (elements) {
2941
2942 // if a Node (the most common case)
2943 if (elements.nodeType) {
2944 root[root.length++] = elements;
2945 } else {
2946 var length = elements.length;
2947
2948 // if an Array or NodeList and not a Window
2949 if (typeof length === 'number' && elements.window !== elements) {
2950 if (length) {
2951 for (var i = 0; i < length; i++) {
2952 root[root.length++] = elements[i];
2953 }
2954 }
2955 } else {
2956 root[root.length++] = elements;
2957 }
2958 }
2959 }
2960}
2961
2962
2963function jqLiteController(element, name) {
2964 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
2965}
2966
2967function jqLiteInheritedData(element, name, value) {
2968 // if element is the document object work with the html element instead
2969 // this makes $(document).scope() possible
2970 if (element.nodeType == NODE_TYPE_DOCUMENT) {
2971 element = element.documentElement;
2972 }
2973 var names = isArray(name) ? name : [name];
2974
2975 while (element) {
2976 for (var i = 0, ii = names.length; i < ii; i++) {
2977 if (isDefined(value = jqLite.data(element, names[i]))) return value;
2978 }
2979
2980 // If dealing with a document fragment node with a host element, and no parent, use the host
2981 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
2982 // to lookup parent controllers.
2983 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
2984 }
2985}
2986
2987function jqLiteEmpty(element) {
2988 jqLiteDealoc(element, true);
2989 while (element.firstChild) {
2990 element.removeChild(element.firstChild);
2991 }
2992}
2993
2994function jqLiteRemove(element, keepData) {
2995 if (!keepData) jqLiteDealoc(element);
2996 var parent = element.parentNode;
2997 if (parent) parent.removeChild(element);
2998}
2999
3000
3001function jqLiteDocumentLoaded(action, win) {
3002 win = win || window;
3003 if (win.document.readyState === 'complete') {
3004 // Force the action to be run async for consistent behaviour
3005 // from the action's point of view
3006 // i.e. it will definitely not be in a $apply
3007 win.setTimeout(action);
3008 } else {
3009 // No need to unbind this handler as load is only ever called once
3010 jqLite(win).on('load', action);
3011 }
3012}
3013
3014//////////////////////////////////////////
3015// Functions which are declared directly.
3016//////////////////////////////////////////
3017var JQLitePrototype = JQLite.prototype = {
3018 ready: function(fn) {
3019 var fired = false;
3020
3021 function trigger() {
3022 if (fired) return;
3023 fired = true;
3024 fn();
3025 }
3026
3027 // check if document is already loaded
3028 if (document.readyState === 'complete') {
3029 setTimeout(trigger);
3030 } else {
3031 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3032 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3033 // jshint -W064
3034 JQLite(window).on('load', trigger); // fallback to window.onload for others
3035 // jshint +W064
3036 }
3037 },
3038 toString: function() {
3039 var value = [];
3040 forEach(this, function(e) { value.push('' + e);});
3041 return '[' + value.join(', ') + ']';
3042 },
3043
3044 eq: function(index) {
3045 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3046 },
3047
3048 length: 0,
3049 push: push,
3050 sort: [].sort,
3051 splice: [].splice
3052};
3053
3054//////////////////////////////////////////
3055// Functions iterating getter/setters.
3056// these functions return self on setter and
3057// value on get.
3058//////////////////////////////////////////
3059var BOOLEAN_ATTR = {};
3060forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3061 BOOLEAN_ATTR[lowercase(value)] = value;
3062});
3063var BOOLEAN_ELEMENTS = {};
3064forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3065 BOOLEAN_ELEMENTS[value] = true;
3066});
3067var ALIASED_ATTR = {
3068 'ngMinlength': 'minlength',
3069 'ngMaxlength': 'maxlength',
3070 'ngMin': 'min',
3071 'ngMax': 'max',
3072 'ngPattern': 'pattern'
3073};
3074
3075function getBooleanAttrName(element, name) {
3076 // check dom last since we will most likely fail on name
3077 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3078
3079 // booleanAttr is here twice to minimize DOM access
3080 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3081}
3082
3083function getAliasedAttrName(name) {
3084 return ALIASED_ATTR[name];
3085}
3086
3087forEach({
3088 data: jqLiteData,
3089 removeData: jqLiteRemoveData,
3090 hasData: jqLiteHasData
3091}, function(fn, name) {
3092 JQLite[name] = fn;
3093});
3094
3095forEach({
3096 data: jqLiteData,
3097 inheritedData: jqLiteInheritedData,
3098
3099 scope: function(element) {
3100 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3101 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3102 },
3103
3104 isolateScope: function(element) {
3105 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3106 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3107 },
3108
3109 controller: jqLiteController,
3110
3111 injector: function(element) {
3112 return jqLiteInheritedData(element, '$injector');
3113 },
3114
3115 removeAttr: function(element, name) {
3116 element.removeAttribute(name);
3117 },
3118
3119 hasClass: jqLiteHasClass,
3120
3121 css: function(element, name, value) {
3122 name = camelCase(name);
3123
3124 if (isDefined(value)) {
3125 element.style[name] = value;
3126 } else {
3127 return element.style[name];
3128 }
3129 },
3130
3131 attr: function(element, name, value) {
3132 var nodeType = element.nodeType;
3133 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3134 return;
3135 }
3136 var lowercasedName = lowercase(name);
3137 if (BOOLEAN_ATTR[lowercasedName]) {
3138 if (isDefined(value)) {
3139 if (!!value) {
3140 element[name] = true;
3141 element.setAttribute(name, lowercasedName);
3142 } else {
3143 element[name] = false;
3144 element.removeAttribute(lowercasedName);
3145 }
3146 } else {
3147 return (element[name] ||
3148 (element.attributes.getNamedItem(name) || noop).specified)
3149 ? lowercasedName
3150 : undefined;
3151 }
3152 } else if (isDefined(value)) {
3153 element.setAttribute(name, value);
3154 } else if (element.getAttribute) {
3155 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3156 // some elements (e.g. Document) don't have get attribute, so return undefined
3157 var ret = element.getAttribute(name, 2);
3158 // normalize non-existing attributes to undefined (as jQuery)
3159 return ret === null ? undefined : ret;
3160 }
3161 },
3162
3163 prop: function(element, name, value) {
3164 if (isDefined(value)) {
3165 element[name] = value;
3166 } else {
3167 return element[name];
3168 }
3169 },
3170
3171 text: (function() {
3172 getText.$dv = '';
3173 return getText;
3174
3175 function getText(element, value) {
3176 if (isUndefined(value)) {
3177 var nodeType = element.nodeType;
3178 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3179 }
3180 element.textContent = value;
3181 }
3182 })(),
3183
3184 val: function(element, value) {
3185 if (isUndefined(value)) {
3186 if (element.multiple && nodeName_(element) === 'select') {
3187 var result = [];
3188 forEach(element.options, function(option) {
3189 if (option.selected) {
3190 result.push(option.value || option.text);
3191 }
3192 });
3193 return result.length === 0 ? null : result;
3194 }
3195 return element.value;
3196 }
3197 element.value = value;
3198 },
3199
3200 html: function(element, value) {
3201 if (isUndefined(value)) {
3202 return element.innerHTML;
3203 }
3204 jqLiteDealoc(element, true);
3205 element.innerHTML = value;
3206 },
3207
3208 empty: jqLiteEmpty
3209}, function(fn, name) {
3210 /**
3211 * Properties: writes return selection, reads return first value
3212 */
3213 JQLite.prototype[name] = function(arg1, arg2) {
3214 var i, key;
3215 var nodeCount = this.length;
3216
3217 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3218 // in a way that survives minification.
3219 // jqLiteEmpty takes no arguments but is a setter.
3220 if (fn !== jqLiteEmpty &&
3221 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3222 if (isObject(arg1)) {
3223
3224 // we are a write, but the object properties are the key/values
3225 for (i = 0; i < nodeCount; i++) {
3226 if (fn === jqLiteData) {
3227 // data() takes the whole object in jQuery
3228 fn(this[i], arg1);
3229 } else {
3230 for (key in arg1) {
3231 fn(this[i], key, arg1[key]);
3232 }
3233 }
3234 }
3235 // return self for chaining
3236 return this;
3237 } else {
3238 // we are a read, so read the first child.
3239 // TODO: do we still need this?
3240 var value = fn.$dv;
3241 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3242 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3243 for (var j = 0; j < jj; j++) {
3244 var nodeValue = fn(this[j], arg1, arg2);
3245 value = value ? value + nodeValue : nodeValue;
3246 }
3247 return value;
3248 }
3249 } else {
3250 // we are a write, so apply to all children
3251 for (i = 0; i < nodeCount; i++) {
3252 fn(this[i], arg1, arg2);
3253 }
3254 // return self for chaining
3255 return this;
3256 }
3257 };
3258});
3259
3260function createEventHandler(element, events) {
3261 var eventHandler = function(event, type) {
3262 // jQuery specific api
3263 event.isDefaultPrevented = function() {
3264 return event.defaultPrevented;
3265 };
3266
3267 var eventFns = events[type || event.type];
3268 var eventFnsLength = eventFns ? eventFns.length : 0;
3269
3270 if (!eventFnsLength) return;
3271
3272 if (isUndefined(event.immediatePropagationStopped)) {
3273 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3274 event.stopImmediatePropagation = function() {
3275 event.immediatePropagationStopped = true;
3276
3277 if (event.stopPropagation) {
3278 event.stopPropagation();
3279 }
3280
3281 if (originalStopImmediatePropagation) {
3282 originalStopImmediatePropagation.call(event);
3283 }
3284 };
3285 }
3286
3287 event.isImmediatePropagationStopped = function() {
3288 return event.immediatePropagationStopped === true;
3289 };
3290
3291 // Copy event handlers in case event handlers array is modified during execution.
3292 if ((eventFnsLength > 1)) {
3293 eventFns = shallowCopy(eventFns);
3294 }
3295
3296 for (var i = 0; i < eventFnsLength; i++) {
3297 if (!event.isImmediatePropagationStopped()) {
3298 eventFns[i].call(element, event);
3299 }
3300 }
3301 };
3302
3303 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3304 // events on `element`
3305 eventHandler.elem = element;
3306 return eventHandler;
3307}
3308
3309//////////////////////////////////////////
3310// Functions iterating traversal.
3311// These functions chain results into a single
3312// selector.
3313//////////////////////////////////////////
3314forEach({
3315 removeData: jqLiteRemoveData,
3316
3317 on: function jqLiteOn(element, type, fn, unsupported) {
3318 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3319
3320 // Do not add event handlers to non-elements because they will not be cleaned up.
3321 if (!jqLiteAcceptsData(element)) {
3322 return;
3323 }
3324
3325 var expandoStore = jqLiteExpandoStore(element, true);
3326 var events = expandoStore.events;
3327 var handle = expandoStore.handle;
3328
3329 if (!handle) {
3330 handle = expandoStore.handle = createEventHandler(element, events);
3331 }
3332
3333 // http://jsperf.com/string-indexof-vs-split
3334 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3335 var i = types.length;
3336
3337 while (i--) {
3338 type = types[i];
3339 var eventFns = events[type];
3340
3341 if (!eventFns) {
3342 events[type] = [];
3343
3344 if (type === 'mouseenter' || type === 'mouseleave') {
3345 // Refer to jQuery's implementation of mouseenter & mouseleave
3346 // Read about mouseenter and mouseleave:
3347 // http://www.quirksmode.org/js/events_mouse.html#link8
3348
3349 jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
3350 var target = this, related = event.relatedTarget;
3351 // For mousenter/leave call the handler if related is outside the target.
3352 // NB: No relatedTarget if the mouse left/entered the browser window
3353 if (!related || (related !== target && !target.contains(related))) {
3354 handle(event, type);
3355 }
3356 });
3357
3358 } else {
3359 if (type !== '$destroy') {
3360 addEventListenerFn(element, type, handle);
3361 }
3362 }
3363 eventFns = events[type];
3364 }
3365 eventFns.push(fn);
3366 }
3367 },
3368
3369 off: jqLiteOff,
3370
3371 one: function(element, type, fn) {
3372 element = jqLite(element);
3373
3374 //add the listener twice so that when it is called
3375 //you can remove the original function and still be
3376 //able to call element.off(ev, fn) normally
3377 element.on(type, function onFn() {
3378 element.off(type, fn);
3379 element.off(type, onFn);
3380 });
3381 element.on(type, fn);
3382 },
3383
3384 replaceWith: function(element, replaceNode) {
3385 var index, parent = element.parentNode;
3386 jqLiteDealoc(element);
3387 forEach(new JQLite(replaceNode), function(node) {
3388 if (index) {
3389 parent.insertBefore(node, index.nextSibling);
3390 } else {
3391 parent.replaceChild(node, element);
3392 }
3393 index = node;
3394 });
3395 },
3396
3397 children: function(element) {
3398 var children = [];
3399 forEach(element.childNodes, function(element) {
3400 if (element.nodeType === NODE_TYPE_ELEMENT) {
3401 children.push(element);
3402 }
3403 });
3404 return children;
3405 },
3406
3407 contents: function(element) {
3408 return element.contentDocument || element.childNodes || [];
3409 },
3410
3411 append: function(element, node) {
3412 var nodeType = element.nodeType;
3413 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3414
3415 node = new JQLite(node);
3416
3417 for (var i = 0, ii = node.length; i < ii; i++) {
3418 var child = node[i];
3419 element.appendChild(child);
3420 }
3421 },
3422
3423 prepend: function(element, node) {
3424 if (element.nodeType === NODE_TYPE_ELEMENT) {
3425 var index = element.firstChild;
3426 forEach(new JQLite(node), function(child) {
3427 element.insertBefore(child, index);
3428 });
3429 }
3430 },
3431
3432 wrap: function(element, wrapNode) {
3433 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3434 var parent = element.parentNode;
3435 if (parent) {
3436 parent.replaceChild(wrapNode, element);
3437 }
3438 wrapNode.appendChild(element);
3439 },
3440
3441 remove: jqLiteRemove,
3442
3443 detach: function(element) {
3444 jqLiteRemove(element, true);
3445 },
3446
3447 after: function(element, newElement) {
3448 var index = element, parent = element.parentNode;
3449 newElement = new JQLite(newElement);
3450
3451 for (var i = 0, ii = newElement.length; i < ii; i++) {
3452 var node = newElement[i];
3453 parent.insertBefore(node, index.nextSibling);
3454 index = node;
3455 }
3456 },
3457
3458 addClass: jqLiteAddClass,
3459 removeClass: jqLiteRemoveClass,
3460
3461 toggleClass: function(element, selector, condition) {
3462 if (selector) {
3463 forEach(selector.split(' '), function(className) {
3464 var classCondition = condition;
3465 if (isUndefined(classCondition)) {
3466 classCondition = !jqLiteHasClass(element, className);
3467 }
3468 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3469 });
3470 }
3471 },
3472
3473 parent: function(element) {
3474 var parent = element.parentNode;
3475 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3476 },
3477
3478 next: function(element) {
3479 return element.nextElementSibling;
3480 },
3481
3482 find: function(element, selector) {
3483 if (element.getElementsByTagName) {
3484 return element.getElementsByTagName(selector);
3485 } else {
3486 return [];
3487 }
3488 },
3489
3490 clone: jqLiteClone,
3491
3492 triggerHandler: function(element, event, extraParameters) {
3493
3494 var dummyEvent, eventFnsCopy, handlerArgs;
3495 var eventName = event.type || event;
3496 var expandoStore = jqLiteExpandoStore(element);
3497 var events = expandoStore && expandoStore.events;
3498 var eventFns = events && events[eventName];
3499
3500 if (eventFns) {
3501 // Create a dummy event to pass to the handlers
3502 dummyEvent = {
3503 preventDefault: function() { this.defaultPrevented = true; },
3504 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3505 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3506 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3507 stopPropagation: noop,
3508 type: eventName,
3509 target: element
3510 };
3511
3512 // If a custom event was provided then extend our dummy event with it
3513 if (event.type) {
3514 dummyEvent = extend(dummyEvent, event);
3515 }
3516
3517 // Copy event handlers in case event handlers array is modified during execution.
3518 eventFnsCopy = shallowCopy(eventFns);
3519 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3520
3521 forEach(eventFnsCopy, function(fn) {
3522 if (!dummyEvent.isImmediatePropagationStopped()) {
3523 fn.apply(element, handlerArgs);
3524 }
3525 });
3526 }
3527 }
3528}, function(fn, name) {
3529 /**
3530 * chaining functions
3531 */
3532 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3533 var value;
3534
3535 for (var i = 0, ii = this.length; i < ii; i++) {
3536 if (isUndefined(value)) {
3537 value = fn(this[i], arg1, arg2, arg3);
3538 if (isDefined(value)) {
3539 // any function which returns a value needs to be wrapped
3540 value = jqLite(value);
3541 }
3542 } else {
3543 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3544 }
3545 }
3546 return isDefined(value) ? value : this;
3547 };
3548
3549 // bind legacy bind/unbind to on/off
3550 JQLite.prototype.bind = JQLite.prototype.on;
3551 JQLite.prototype.unbind = JQLite.prototype.off;
3552});
3553
3554
3555// Provider for private $$jqLite service
3556function $$jqLiteProvider() {
3557 this.$get = function $$jqLite() {
3558 return extend(JQLite, {
3559 hasClass: function(node, classes) {
3560 if (node.attr) node = node[0];
3561 return jqLiteHasClass(node, classes);
3562 },
3563 addClass: function(node, classes) {
3564 if (node.attr) node = node[0];
3565 return jqLiteAddClass(node, classes);
3566 },
3567 removeClass: function(node, classes) {
3568 if (node.attr) node = node[0];
3569 return jqLiteRemoveClass(node, classes);
3570 }
3571 });
3572 };
3573}
3574
3575/**
3576 * Computes a hash of an 'obj'.
3577 * Hash of a:
3578 * string is string
3579 * number is number as string
3580 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3581 * that is also assigned to the $$hashKey property of the object.
3582 *
3583 * @param obj
3584 * @returns {string} hash string such that the same input will have the same hash string.
3585 * The resulting string key is in 'type:hashKey' format.
3586 */
3587function hashKey(obj, nextUidFn) {
3588 var key = obj && obj.$$hashKey;
3589
3590 if (key) {
3591 if (typeof key === 'function') {
3592 key = obj.$$hashKey();
3593 }
3594 return key;
3595 }
3596
3597 var objType = typeof obj;
3598 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3599 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3600 } else {
3601 key = objType + ':' + obj;
3602 }
3603
3604 return key;
3605}
3606
3607/**
3608 * HashMap which can use objects as keys
3609 */
3610function HashMap(array, isolatedUid) {
3611 if (isolatedUid) {
3612 var uid = 0;
3613 this.nextUid = function() {
3614 return ++uid;
3615 };
3616 }
3617 forEach(array, this.put, this);
3618}
3619HashMap.prototype = {
3620 /**
3621 * Store key value pair
3622 * @param key key to store can be any type
3623 * @param value value to store can be any type
3624 */
3625 put: function(key, value) {
3626 this[hashKey(key, this.nextUid)] = value;
3627 },
3628
3629 /**
3630 * @param key
3631 * @returns {Object} the value for the key
3632 */
3633 get: function(key) {
3634 return this[hashKey(key, this.nextUid)];
3635 },
3636
3637 /**
3638 * Remove the key/value pair
3639 * @param key
3640 */
3641 remove: function(key) {
3642 var value = this[key = hashKey(key, this.nextUid)];
3643 delete this[key];
3644 return value;
3645 }
3646};
3647
3648var $$HashMapProvider = [function() {
3649 this.$get = [function() {
3650 return HashMap;
3651 }];
3652}];
3653
3654/**
3655 * @ngdoc function
3656 * @module ng
3657 * @name angular.injector
3658 * @kind function
3659 *
3660 * @description
3661 * Creates an injector object that can be used for retrieving services as well as for
3662 * dependency injection (see {@link guide/di dependency injection}).
3663 *
3664 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3665 * {@link angular.module}. The `ng` module must be explicitly added.
3666 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3667 * disallows argument name annotation inference.
3668 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3669 *
3670 * @example
3671 * Typical usage
3672 * ```js
3673 * // create an injector
3674 * var $injector = angular.injector(['ng']);
3675 *
3676 * // use the injector to kick off your application
3677 * // use the type inference to auto inject arguments, or use implicit injection
3678 * $injector.invoke(function($rootScope, $compile, $document) {
3679 * $compile($document)($rootScope);
3680 * $rootScope.$digest();
3681 * });
3682 * ```
3683 *
3684 * Sometimes you want to get access to the injector of a currently running Angular app
3685 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3686 * application has been bootstrapped. You can do this using the extra `injector()` added
3687 * to JQuery/jqLite elements. See {@link angular.element}.
3688 *
3689 * *This is fairly rare but could be the case if a third party library is injecting the
3690 * markup.*
3691 *
3692 * In the following example a new block of HTML containing a `ng-controller`
3693 * directive is added to the end of the document body by JQuery. We then compile and link
3694 * it into the current AngularJS scope.
3695 *
3696 * ```js
3697 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3698 * $(document.body).append($div);
3699 *
3700 * angular.element(document).injector().invoke(function($compile) {
3701 * var scope = angular.element($div).scope();
3702 * $compile($div)(scope);
3703 * });
3704 * ```
3705 */
3706
3707
3708/**
3709 * @ngdoc module
3710 * @name auto
3711 * @description
3712 *
3713 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3714 */
3715
3716var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3717var FN_ARG_SPLIT = /,/;
3718var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3719var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3720var $injectorMinErr = minErr('$injector');
3721
3722function anonFn(fn) {
3723 // For anonymous functions, showing at the very least the function signature can help in
3724 // debugging.
3725 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3726 args = fnText.match(FN_ARGS);
3727 if (args) {
3728 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3729 }
3730 return 'fn';
3731}
3732
3733function annotate(fn, strictDi, name) {
3734 var $inject,
3735 fnText,
3736 argDecl,
3737 last;
3738
3739 if (typeof fn === 'function') {
3740 if (!($inject = fn.$inject)) {
3741 $inject = [];
3742 if (fn.length) {
3743 if (strictDi) {
3744 if (!isString(name) || !name) {
3745 name = fn.name || anonFn(fn);
3746 }
3747 throw $injectorMinErr('strictdi',
3748 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3749 }
3750 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3751 argDecl = fnText.match(FN_ARGS);
3752 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3753 arg.replace(FN_ARG, function(all, underscore, name) {
3754 $inject.push(name);
3755 });
3756 });
3757 }
3758 fn.$inject = $inject;
3759 }
3760 } else if (isArray(fn)) {
3761 last = fn.length - 1;
3762 assertArgFn(fn[last], 'fn');
3763 $inject = fn.slice(0, last);
3764 } else {
3765 assertArgFn(fn, 'fn', true);
3766 }
3767 return $inject;
3768}
3769
3770///////////////////////////////////////
3771
3772/**
3773 * @ngdoc service
3774 * @name $injector
3775 *
3776 * @description
3777 *
3778 * `$injector` is used to retrieve object instances as defined by
3779 * {@link auto.$provide provider}, instantiate types, invoke methods,
3780 * and load modules.
3781 *
3782 * The following always holds true:
3783 *
3784 * ```js
3785 * var $injector = angular.injector();
3786 * expect($injector.get('$injector')).toBe($injector);
3787 * expect($injector.invoke(function($injector) {
3788 * return $injector;
3789 * })).toBe($injector);
3790 * ```
3791 *
3792 * # Injection Function Annotation
3793 *
3794 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3795 * following are all valid ways of annotating function with injection arguments and are equivalent.
3796 *
3797 * ```js
3798 * // inferred (only works if code not minified/obfuscated)
3799 * $injector.invoke(function(serviceA){});
3800 *
3801 * // annotated
3802 * function explicit(serviceA) {};
3803 * explicit.$inject = ['serviceA'];
3804 * $injector.invoke(explicit);
3805 *
3806 * // inline
3807 * $injector.invoke(['serviceA', function(serviceA){}]);
3808 * ```
3809 *
3810 * ## Inference
3811 *
3812 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3813 * can then be parsed and the function arguments can be extracted. This method of discovering
3814 * annotations is disallowed when the injector is in strict mode.
3815 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3816 * argument names.
3817 *
3818 * ## `$inject` Annotation
3819 * By adding an `$inject` property onto a function the injection parameters can be specified.
3820 *
3821 * ## Inline
3822 * As an array of injection names, where the last item in the array is the function to call.
3823 */
3824
3825/**
3826 * @ngdoc method
3827 * @name $injector#get
3828 *
3829 * @description
3830 * Return an instance of the service.
3831 *
3832 * @param {string} name The name of the instance to retrieve.
3833 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3834 * @return {*} The instance.
3835 */
3836
3837/**
3838 * @ngdoc method
3839 * @name $injector#invoke
3840 *
3841 * @description
3842 * Invoke the method and supply the method arguments from the `$injector`.
3843 *
3844 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3845 * injected according to the {@link guide/di $inject Annotation} rules.
3846 * @param {Object=} self The `this` for the invoked method.
3847 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3848 * object first, before the `$injector` is consulted.
3849 * @returns {*} the value returned by the invoked `fn` function.
3850 */
3851
3852/**
3853 * @ngdoc method
3854 * @name $injector#has
3855 *
3856 * @description
3857 * Allows the user to query if the particular service exists.
3858 *
3859 * @param {string} name Name of the service to query.
3860 * @returns {boolean} `true` if injector has given service.
3861 */
3862
3863/**
3864 * @ngdoc method
3865 * @name $injector#instantiate
3866 * @description
3867 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3868 * operator, and supplies all of the arguments to the constructor function as specified by the
3869 * constructor annotation.
3870 *
3871 * @param {Function} Type Annotated constructor function.
3872 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3873 * object first, before the `$injector` is consulted.
3874 * @returns {Object} new instance of `Type`.
3875 */
3876
3877/**
3878 * @ngdoc method
3879 * @name $injector#annotate
3880 *
3881 * @description
3882 * Returns an array of service names which the function is requesting for injection. This API is
3883 * used by the injector to determine which services need to be injected into the function when the
3884 * function is invoked. There are three ways in which the function can be annotated with the needed
3885 * dependencies.
3886 *
3887 * # Argument names
3888 *
3889 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3890 * by converting the function into a string using `toString()` method and extracting the argument
3891 * names.
3892 * ```js
3893 * // Given
3894 * function MyController($scope, $route) {
3895 * // ...
3896 * }
3897 *
3898 * // Then
3899 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
3900 * ```
3901 *
3902 * You can disallow this method by using strict injection mode.
3903 *
3904 * This method does not work with code minification / obfuscation. For this reason the following
3905 * annotation strategies are supported.
3906 *
3907 * # The `$inject` property
3908 *
3909 * If a function has an `$inject` property and its value is an array of strings, then the strings
3910 * represent names of services to be injected into the function.
3911 * ```js
3912 * // Given
3913 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
3914 * // ...
3915 * }
3916 * // Define function dependencies
3917 * MyController['$inject'] = ['$scope', '$route'];
3918 *
3919 * // Then
3920 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
3921 * ```
3922 *
3923 * # The array notation
3924 *
3925 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
3926 * is very inconvenient. In these situations using the array notation to specify the dependencies in
3927 * a way that survives minification is a better choice:
3928 *
3929 * ```js
3930 * // We wish to write this (not minification / obfuscation safe)
3931 * injector.invoke(function($compile, $rootScope) {
3932 * // ...
3933 * });
3934 *
3935 * // We are forced to write break inlining
3936 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
3937 * // ...
3938 * };
3939 * tmpFn.$inject = ['$compile', '$rootScope'];
3940 * injector.invoke(tmpFn);
3941 *
3942 * // To better support inline function the inline annotation is supported
3943 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
3944 * // ...
3945 * }]);
3946 *
3947 * // Therefore
3948 * expect(injector.annotate(
3949 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
3950 * ).toEqual(['$compile', '$rootScope']);
3951 * ```
3952 *
3953 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
3954 * be retrieved as described above.
3955 *
3956 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
3957 *
3958 * @returns {Array.<string>} The names of the services which the function requires.
3959 */
3960
3961
3962
3963
3964/**
3965 * @ngdoc service
3966 * @name $provide
3967 *
3968 * @description
3969 *
3970 * The {@link auto.$provide $provide} service has a number of methods for registering components
3971 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
3972 * {@link angular.Module}.
3973 *
3974 * An Angular **service** is a singleton object created by a **service factory**. These **service
3975 * factories** are functions which, in turn, are created by a **service provider**.
3976 * The **service providers** are constructor functions. When instantiated they must contain a
3977 * property called `$get`, which holds the **service factory** function.
3978 *
3979 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
3980 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
3981 * function to get the instance of the **service**.
3982 *
3983 * Often services have no configuration options and there is no need to add methods to the service
3984 * provider. The provider will be no more than a constructor function with a `$get` property. For
3985 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
3986 * services without specifying a provider.
3987 *
3988 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
3989 * {@link auto.$injector $injector}
3990 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
3991 * providers and services.
3992 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
3993 * services, not providers.
3994 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
3995 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
3996 * given factory function.
3997 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
3998 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
3999 * a new object using the given constructor function.
4000 *
4001 * See the individual methods for more information and examples.
4002 */
4003
4004/**
4005 * @ngdoc method
4006 * @name $provide#provider
4007 * @description
4008 *
4009 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4010 * are constructor functions, whose instances are responsible for "providing" a factory for a
4011 * service.
4012 *
4013 * Service provider names start with the name of the service they provide followed by `Provider`.
4014 * For example, the {@link ng.$log $log} service has a provider called
4015 * {@link ng.$logProvider $logProvider}.
4016 *
4017 * Service provider objects can have additional methods which allow configuration of the provider
4018 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4019 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4020 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4021 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4022 * console or not.
4023 *
4024 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4025 'Provider'` key.
4026 * @param {(Object|function())} provider If the provider is:
4027 *
4028 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4029 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4030 * - `Constructor`: a new instance of the provider will be created using
4031 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4032 *
4033 * @returns {Object} registered provider instance
4034
4035 * @example
4036 *
4037 * The following example shows how to create a simple event tracking service and register it using
4038 * {@link auto.$provide#provider $provide.provider()}.
4039 *
4040 * ```js
4041 * // Define the eventTracker provider
4042 * function EventTrackerProvider() {
4043 * var trackingUrl = '/track';
4044 *
4045 * // A provider method for configuring where the tracked events should been saved
4046 * this.setTrackingUrl = function(url) {
4047 * trackingUrl = url;
4048 * };
4049 *
4050 * // The service factory function
4051 * this.$get = ['$http', function($http) {
4052 * var trackedEvents = {};
4053 * return {
4054 * // Call this to track an event
4055 * event: function(event) {
4056 * var count = trackedEvents[event] || 0;
4057 * count += 1;
4058 * trackedEvents[event] = count;
4059 * return count;
4060 * },
4061 * // Call this to save the tracked events to the trackingUrl
4062 * save: function() {
4063 * $http.post(trackingUrl, trackedEvents);
4064 * }
4065 * };
4066 * }];
4067 * }
4068 *
4069 * describe('eventTracker', function() {
4070 * var postSpy;
4071 *
4072 * beforeEach(module(function($provide) {
4073 * // Register the eventTracker provider
4074 * $provide.provider('eventTracker', EventTrackerProvider);
4075 * }));
4076 *
4077 * beforeEach(module(function(eventTrackerProvider) {
4078 * // Configure eventTracker provider
4079 * eventTrackerProvider.setTrackingUrl('/custom-track');
4080 * }));
4081 *
4082 * it('tracks events', inject(function(eventTracker) {
4083 * expect(eventTracker.event('login')).toEqual(1);
4084 * expect(eventTracker.event('login')).toEqual(2);
4085 * }));
4086 *
4087 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4088 * postSpy = spyOn($http, 'post');
4089 * eventTracker.event('login');
4090 * eventTracker.save();
4091 * expect(postSpy).toHaveBeenCalled();
4092 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4093 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4094 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4095 * }));
4096 * });
4097 * ```
4098 */
4099
4100/**
4101 * @ngdoc method
4102 * @name $provide#factory
4103 * @description
4104 *
4105 * Register a **service factory**, which will be called to return the service instance.
4106 * This is short for registering a service where its provider consists of only a `$get` property,
4107 * which is the given service factory function.
4108 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4109 * configure your service in a provider.
4110 *
4111 * @param {string} name The name of the instance.
4112 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4113 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4114 * @returns {Object} registered provider instance
4115 *
4116 * @example
4117 * Here is an example of registering a service
4118 * ```js
4119 * $provide.factory('ping', ['$http', function($http) {
4120 * return function ping() {
4121 * return $http.send('/ping');
4122 * };
4123 * }]);
4124 * ```
4125 * You would then inject and use this service like this:
4126 * ```js
4127 * someModule.controller('Ctrl', ['ping', function(ping) {
4128 * ping();
4129 * }]);
4130 * ```
4131 */
4132
4133
4134/**
4135 * @ngdoc method
4136 * @name $provide#service
4137 * @description
4138 *
4139 * Register a **service constructor**, which will be invoked with `new` to create the service
4140 * instance.
4141 * This is short for registering a service where its provider's `$get` property is the service
4142 * constructor function that will be used to instantiate the service instance.
4143 *
4144 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4145 * as a type/class.
4146 *
4147 * @param {string} name The name of the instance.
4148 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4149 * that will be instantiated.
4150 * @returns {Object} registered provider instance
4151 *
4152 * @example
4153 * Here is an example of registering a service using
4154 * {@link auto.$provide#service $provide.service(class)}.
4155 * ```js
4156 * var Ping = function($http) {
4157 * this.$http = $http;
4158 * };
4159 *
4160 * Ping.$inject = ['$http'];
4161 *
4162 * Ping.prototype.send = function() {
4163 * return this.$http.get('/ping');
4164 * };
4165 * $provide.service('ping', Ping);
4166 * ```
4167 * You would then inject and use this service like this:
4168 * ```js
4169 * someModule.controller('Ctrl', ['ping', function(ping) {
4170 * ping.send();
4171 * }]);
4172 * ```
4173 */
4174
4175
4176/**
4177 * @ngdoc method
4178 * @name $provide#value
4179 * @description
4180 *
4181 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4182 * number, an array, an object or a function. This is short for registering a service where its
4183 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4184 * service**.
4185 *
4186 * Value services are similar to constant services, except that they cannot be injected into a
4187 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4188 * an Angular
4189 * {@link auto.$provide#decorator decorator}.
4190 *
4191 * @param {string} name The name of the instance.
4192 * @param {*} value The value.
4193 * @returns {Object} registered provider instance
4194 *
4195 * @example
4196 * Here are some examples of creating value services.
4197 * ```js
4198 * $provide.value('ADMIN_USER', 'admin');
4199 *
4200 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4201 *
4202 * $provide.value('halfOf', function(value) {
4203 * return value / 2;
4204 * });
4205 * ```
4206 */
4207
4208
4209/**
4210 * @ngdoc method
4211 * @name $provide#constant
4212 * @description
4213 *
4214 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4215 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4216 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4217 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4218 *
4219 * @param {string} name The name of the constant.
4220 * @param {*} value The constant value.
4221 * @returns {Object} registered instance
4222 *
4223 * @example
4224 * Here a some examples of creating constants:
4225 * ```js
4226 * $provide.constant('SHARD_HEIGHT', 306);
4227 *
4228 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4229 *
4230 * $provide.constant('double', function(value) {
4231 * return value * 2;
4232 * });
4233 * ```
4234 */
4235
4236
4237/**
4238 * @ngdoc method
4239 * @name $provide#decorator
4240 * @description
4241 *
4242 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4243 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4244 * service. The object returned by the decorator may be the original service, or a new service
4245 * object which replaces or wraps and delegates to the original service.
4246 *
4247 * @param {string} name The name of the service to decorate.
4248 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4249 * instantiated and should return the decorated service instance. The function is called using
4250 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4251 * Local injection arguments:
4252 *
4253 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4254 * decorated or delegated to.
4255 *
4256 * @example
4257 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4258 * calls to {@link ng.$log#error $log.warn()}.
4259 * ```js
4260 * $provide.decorator('$log', ['$delegate', function($delegate) {
4261 * $delegate.warn = $delegate.error;
4262 * return $delegate;
4263 * }]);
4264 * ```
4265 */
4266
4267
4268function createInjector(modulesToLoad, strictDi) {
4269 strictDi = (strictDi === true);
4270 var INSTANTIATING = {},
4271 providerSuffix = 'Provider',
4272 path = [],
4273 loadedModules = new HashMap([], true),
4274 providerCache = {
4275 $provide: {
4276 provider: supportObject(provider),
4277 factory: supportObject(factory),
4278 service: supportObject(service),
4279 value: supportObject(value),
4280 constant: supportObject(constant),
4281 decorator: decorator
4282 }
4283 },
4284 providerInjector = (providerCache.$injector =
4285 createInternalInjector(providerCache, function(serviceName, caller) {
4286 if (angular.isString(caller)) {
4287 path.push(caller);
4288 }
4289 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4290 })),
4291 instanceCache = {},
4292 instanceInjector = (instanceCache.$injector =
4293 createInternalInjector(instanceCache, function(serviceName, caller) {
4294 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4295 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4296 }));
4297
4298
4299 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4300
4301 return instanceInjector;
4302
4303 ////////////////////////////////////
4304 // $provider
4305 ////////////////////////////////////
4306
4307 function supportObject(delegate) {
4308 return function(key, value) {
4309 if (isObject(key)) {
4310 forEach(key, reverseParams(delegate));
4311 } else {
4312 return delegate(key, value);
4313 }
4314 };
4315 }
4316
4317 function provider(name, provider_) {
4318 assertNotHasOwnProperty(name, 'service');
4319 if (isFunction(provider_) || isArray(provider_)) {
4320 provider_ = providerInjector.instantiate(provider_);
4321 }
4322 if (!provider_.$get) {
4323 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4324 }
4325 return providerCache[name + providerSuffix] = provider_;
4326 }
4327
4328 function enforceReturnValue(name, factory) {
4329 return function enforcedReturnValue() {
4330 var result = instanceInjector.invoke(factory, this);
4331 if (isUndefined(result)) {
4332 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4333 }
4334 return result;
4335 };
4336 }
4337
4338 function factory(name, factoryFn, enforce) {
4339 return provider(name, {
4340 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4341 });
4342 }
4343
4344 function service(name, constructor) {
4345 return factory(name, ['$injector', function($injector) {
4346 return $injector.instantiate(constructor);
4347 }]);
4348 }
4349
4350 function value(name, val) { return factory(name, valueFn(val), false); }
4351
4352 function constant(name, value) {
4353 assertNotHasOwnProperty(name, 'constant');
4354 providerCache[name] = value;
4355 instanceCache[name] = value;
4356 }
4357
4358 function decorator(serviceName, decorFn) {
4359 var origProvider = providerInjector.get(serviceName + providerSuffix),
4360 orig$get = origProvider.$get;
4361
4362 origProvider.$get = function() {
4363 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4364 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4365 };
4366 }
4367
4368 ////////////////////////////////////
4369 // Module Loading
4370 ////////////////////////////////////
4371 function loadModules(modulesToLoad) {
4372 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4373 var runBlocks = [], moduleFn;
4374 forEach(modulesToLoad, function(module) {
4375 if (loadedModules.get(module)) return;
4376 loadedModules.put(module, true);
4377
4378 function runInvokeQueue(queue) {
4379 var i, ii;
4380 for (i = 0, ii = queue.length; i < ii; i++) {
4381 var invokeArgs = queue[i],
4382 provider = providerInjector.get(invokeArgs[0]);
4383
4384 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4385 }
4386 }
4387
4388 try {
4389 if (isString(module)) {
4390 moduleFn = angularModule(module);
4391 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4392 runInvokeQueue(moduleFn._invokeQueue);
4393 runInvokeQueue(moduleFn._configBlocks);
4394 } else if (isFunction(module)) {
4395 runBlocks.push(providerInjector.invoke(module));
4396 } else if (isArray(module)) {
4397 runBlocks.push(providerInjector.invoke(module));
4398 } else {
4399 assertArgFn(module, 'module');
4400 }
4401 } catch (e) {
4402 if (isArray(module)) {
4403 module = module[module.length - 1];
4404 }
4405 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4406 // Safari & FF's stack traces don't contain error.message content
4407 // unlike those of Chrome and IE
4408 // So if stack doesn't contain message, we create a new string that contains both.
4409 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4410 /* jshint -W022 */
4411 e = e.message + '\n' + e.stack;
4412 }
4413 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4414 module, e.stack || e.message || e);
4415 }
4416 });
4417 return runBlocks;
4418 }
4419
4420 ////////////////////////////////////
4421 // internal Injector
4422 ////////////////////////////////////
4423
4424 function createInternalInjector(cache, factory) {
4425
4426 function getService(serviceName, caller) {
4427 if (cache.hasOwnProperty(serviceName)) {
4428 if (cache[serviceName] === INSTANTIATING) {
4429 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4430 serviceName + ' <- ' + path.join(' <- '));
4431 }
4432 return cache[serviceName];
4433 } else {
4434 try {
4435 path.unshift(serviceName);
4436 cache[serviceName] = INSTANTIATING;
4437 return cache[serviceName] = factory(serviceName, caller);
4438 } catch (err) {
4439 if (cache[serviceName] === INSTANTIATING) {
4440 delete cache[serviceName];
4441 }
4442 throw err;
4443 } finally {
4444 path.shift();
4445 }
4446 }
4447 }
4448
4449 function invoke(fn, self, locals, serviceName) {
4450 if (typeof locals === 'string') {
4451 serviceName = locals;
4452 locals = null;
4453 }
4454
4455 var args = [],
4456 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4457 length, i,
4458 key;
4459
4460 for (i = 0, length = $inject.length; i < length; i++) {
4461 key = $inject[i];
4462 if (typeof key !== 'string') {
4463 throw $injectorMinErr('itkn',
4464 'Incorrect injection token! Expected service name as string, got {0}', key);
4465 }
4466 args.push(
4467 locals && locals.hasOwnProperty(key)
4468 ? locals[key]
4469 : getService(key, serviceName)
4470 );
4471 }
4472 if (isArray(fn)) {
4473 fn = fn[length];
4474 }
4475
4476 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4477 // #5388
4478 return fn.apply(self, args);
4479 }
4480
4481 function instantiate(Type, locals, serviceName) {
4482 // Check if Type is annotated and use just the given function at n-1 as parameter
4483 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4484 // Object creation: http://jsperf.com/create-constructor/2
4485 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4486 var returnedValue = invoke(Type, instance, locals, serviceName);
4487
4488 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4489 }
4490
4491 return {
4492 invoke: invoke,
4493 instantiate: instantiate,
4494 get: getService,
4495 annotate: createInjector.$$annotate,
4496 has: function(name) {
4497 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4498 }
4499 };
4500 }
4501}
4502
4503createInjector.$$annotate = annotate;
4504
4505/**
4506 * @ngdoc provider
4507 * @name $anchorScrollProvider
4508 *
4509 * @description
4510 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4511 * {@link ng.$location#hash $location.hash()} changes.
4512 */
4513function $AnchorScrollProvider() {
4514
4515 var autoScrollingEnabled = true;
4516
4517 /**
4518 * @ngdoc method
4519 * @name $anchorScrollProvider#disableAutoScrolling
4520 *
4521 * @description
4522 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4523 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4524 * Use this method to disable automatic scrolling.
4525 *
4526 * If automatic scrolling is disabled, one must explicitly call
4527 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4528 * current hash.
4529 */
4530 this.disableAutoScrolling = function() {
4531 autoScrollingEnabled = false;
4532 };
4533
4534 /**
4535 * @ngdoc service
4536 * @name $anchorScroll
4537 * @kind function
4538 * @requires $window
4539 * @requires $location
4540 * @requires $rootScope
4541 *
4542 * @description
4543 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4544 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4545 * in the
4546 * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
4547 *
4548 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4549 * match any anchor whenever it changes. This can be disabled by calling
4550 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4551 *
4552 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4553 * vertical scroll-offset (either fixed or dynamic).
4554 *
4555 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4556 * {@link ng.$location#hash $location.hash()} will be used.
4557 *
4558 * @property {(number|function|jqLite)} yOffset
4559 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4560 * positioned elements at the top of the page, such as navbars, headers etc.
4561 *
4562 * `yOffset` can be specified in various ways:
4563 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4564 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4565 * a number representing the offset (in pixels).<br /><br />
4566 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4567 * the top of the page to the element's bottom will be used as offset.<br />
4568 * **Note**: The element will be taken into account only as long as its `position` is set to
4569 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4570 * their height and/or positioning according to the viewport's size.
4571 *
4572 * <br />
4573 * <div class="alert alert-warning">
4574 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4575 * not some child element.
4576 * </div>
4577 *
4578 * @example
4579 <example module="anchorScrollExample">
4580 <file name="index.html">
4581 <div id="scrollArea" ng-controller="ScrollController">
4582 <a ng-click="gotoBottom()">Go to bottom</a>
4583 <a id="bottom"></a> You're at the bottom!
4584 </div>
4585 </file>
4586 <file name="script.js">
4587 angular.module('anchorScrollExample', [])
4588 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4589 function ($scope, $location, $anchorScroll) {
4590 $scope.gotoBottom = function() {
4591 // set the location.hash to the id of
4592 // the element you wish to scroll to.
4593 $location.hash('bottom');
4594
4595 // call $anchorScroll()
4596 $anchorScroll();
4597 };
4598 }]);
4599 </file>
4600 <file name="style.css">
4601 #scrollArea {
4602 height: 280px;
4603 overflow: auto;
4604 }
4605
4606 #bottom {
4607 display: block;
4608 margin-top: 2000px;
4609 }
4610 </file>
4611 </example>
4612 *
4613 * <hr />
4614 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4615 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4616 *
4617 * @example
4618 <example module="anchorScrollOffsetExample">
4619 <file name="index.html">
4620 <div class="fixed-header" ng-controller="headerCtrl">
4621 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4622 Go to anchor {{x}}
4623 </a>
4624 </div>
4625 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4626 Anchor {{x}} of 5
4627 </div>
4628 </file>
4629 <file name="script.js">
4630 angular.module('anchorScrollOffsetExample', [])
4631 .run(['$anchorScroll', function($anchorScroll) {
4632 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4633 }])
4634 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4635 function ($anchorScroll, $location, $scope) {
4636 $scope.gotoAnchor = function(x) {
4637 var newHash = 'anchor' + x;
4638 if ($location.hash() !== newHash) {
4639 // set the $location.hash to `newHash` and
4640 // $anchorScroll will automatically scroll to it
4641 $location.hash('anchor' + x);
4642 } else {
4643 // call $anchorScroll() explicitly,
4644 // since $location.hash hasn't changed
4645 $anchorScroll();
4646 }
4647 };
4648 }
4649 ]);
4650 </file>
4651 <file name="style.css">
4652 body {
4653 padding-top: 50px;
4654 }
4655
4656 .anchor {
4657 border: 2px dashed DarkOrchid;
4658 padding: 10px 10px 200px 10px;
4659 }
4660
4661 .fixed-header {
4662 background-color: rgba(0, 0, 0, 0.2);
4663 height: 50px;
4664 position: fixed;
4665 top: 0; left: 0; right: 0;
4666 }
4667
4668 .fixed-header > a {
4669 display: inline-block;
4670 margin: 5px 15px;
4671 }
4672 </file>
4673 </example>
4674 */
4675 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4676 var document = $window.document;
4677
4678 // Helper function to get first anchor from a NodeList
4679 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4680 // and working in all supported browsers.)
4681 function getFirstAnchor(list) {
4682 var result = null;
4683 Array.prototype.some.call(list, function(element) {
4684 if (nodeName_(element) === 'a') {
4685 result = element;
4686 return true;
4687 }
4688 });
4689 return result;
4690 }
4691
4692 function getYOffset() {
4693
4694 var offset = scroll.yOffset;
4695
4696 if (isFunction(offset)) {
4697 offset = offset();
4698 } else if (isElement(offset)) {
4699 var elem = offset[0];
4700 var style = $window.getComputedStyle(elem);
4701 if (style.position !== 'fixed') {
4702 offset = 0;
4703 } else {
4704 offset = elem.getBoundingClientRect().bottom;
4705 }
4706 } else if (!isNumber(offset)) {
4707 offset = 0;
4708 }
4709
4710 return offset;
4711 }
4712
4713 function scrollTo(elem) {
4714 if (elem) {
4715 elem.scrollIntoView();
4716
4717 var offset = getYOffset();
4718
4719 if (offset) {
4720 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4721 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4722 // top of the viewport.
4723 //
4724 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4725 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4726 // way down the page.
4727 //
4728 // This is often the case for elements near the bottom of the page.
4729 //
4730 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4731 // the top of the element and the offset, which is enough to align the top of `elem` at the
4732 // desired position.
4733 var elemTop = elem.getBoundingClientRect().top;
4734 $window.scrollBy(0, elemTop - offset);
4735 }
4736 } else {
4737 $window.scrollTo(0, 0);
4738 }
4739 }
4740
4741 function scroll(hash) {
4742 hash = isString(hash) ? hash : $location.hash();
4743 var elm;
4744
4745 // empty hash, scroll to the top of the page
4746 if (!hash) scrollTo(null);
4747
4748 // element with given id
4749 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4750
4751 // first anchor with given name :-D
4752 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4753
4754 // no element and hash == 'top', scroll to the top of the page
4755 else if (hash === 'top') scrollTo(null);
4756 }
4757
4758 // does not scroll when user clicks on anchor link that is currently on
4759 // (no url change, no $location.hash() change), browser native does scroll
4760 if (autoScrollingEnabled) {
4761 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4762 function autoScrollWatchAction(newVal, oldVal) {
4763 // skip the initial scroll if $location.hash is empty
4764 if (newVal === oldVal && newVal === '') return;
4765
4766 jqLiteDocumentLoaded(function() {
4767 $rootScope.$evalAsync(scroll);
4768 });
4769 });
4770 }
4771
4772 return scroll;
4773 }];
4774}
4775
4776var $animateMinErr = minErr('$animate');
4777var ELEMENT_NODE = 1;
4778var NG_ANIMATE_CLASSNAME = 'ng-animate';
4779
4780function mergeClasses(a,b) {
4781 if (!a && !b) return '';
4782 if (!a) return b;
4783 if (!b) return a;
4784 if (isArray(a)) a = a.join(' ');
4785 if (isArray(b)) b = b.join(' ');
4786 return a + ' ' + b;
4787}
4788
4789function extractElementNode(element) {
4790 for (var i = 0; i < element.length; i++) {
4791 var elm = element[i];
4792 if (elm.nodeType === ELEMENT_NODE) {
4793 return elm;
4794 }
4795 }
4796}
4797
4798function splitClasses(classes) {
4799 if (isString(classes)) {
4800 classes = classes.split(' ');
4801 }
4802
4803 // Use createMap() to prevent class assumptions involving property names in
4804 // Object.prototype
4805 var obj = createMap();
4806 forEach(classes, function(klass) {
4807 // sometimes the split leaves empty string values
4808 // incase extra spaces were applied to the options
4809 if (klass.length) {
4810 obj[klass] = true;
4811 }
4812 });
4813 return obj;
4814}
4815
4816// if any other type of options value besides an Object value is
4817// passed into the $animate.method() animation then this helper code
4818// will be run which will ignore it. While this patch is not the
4819// greatest solution to this, a lot of existing plugins depend on
4820// $animate to either call the callback (< 1.2) or return a promise
4821// that can be changed. This helper function ensures that the options
4822// are wiped clean incase a callback function is provided.
4823function prepareAnimateOptions(options) {
4824 return isObject(options)
4825 ? options
4826 : {};
4827}
4828
4829var $$CoreAnimateRunnerProvider = function() {
4830 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4831 function AnimateRunner() {}
4832 AnimateRunner.all = noop;
4833 AnimateRunner.chain = noop;
4834 AnimateRunner.prototype = {
4835 end: noop,
4836 cancel: noop,
4837 resume: noop,
4838 pause: noop,
4839 complete: noop,
4840 then: function(pass, fail) {
4841 return $q(function(resolve) {
4842 $$rAF(function() {
4843 resolve();
4844 });
4845 }).then(pass, fail);
4846 }
4847 };
4848 return AnimateRunner;
4849 }];
4850};
4851
4852// this is prefixed with Core since it conflicts with
4853// the animateQueueProvider defined in ngAnimate/animateQueue.js
4854var $$CoreAnimateQueueProvider = function() {
4855 var postDigestQueue = new HashMap();
4856 var postDigestElements = [];
4857
4858 this.$get = ['$$AnimateRunner', '$rootScope',
4859 function($$AnimateRunner, $rootScope) {
4860 return {
4861 enabled: noop,
4862 on: noop,
4863 off: noop,
4864 pin: noop,
4865
4866 push: function(element, event, options, domOperation) {
4867 domOperation && domOperation();
4868
4869 options = options || {};
4870 options.from && element.css(options.from);
4871 options.to && element.css(options.to);
4872
4873 if (options.addClass || options.removeClass) {
4874 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
4875 }
4876
4877 return new $$AnimateRunner(); // jshint ignore:line
4878 }
4879 };
4880
4881
4882 function updateData(data, classes, value) {
4883 var changed = false;
4884 if (classes) {
4885 classes = isString(classes) ? classes.split(' ') :
4886 isArray(classes) ? classes : [];
4887 forEach(classes, function(className) {
4888 if (className) {
4889 changed = true;
4890 data[className] = value;
4891 }
4892 });
4893 }
4894 return changed;
4895 }
4896
4897 function handleCSSClassChanges() {
4898 forEach(postDigestElements, function(element) {
4899 var data = postDigestQueue.get(element);
4900 if (data) {
4901 var existing = splitClasses(element.attr('class'));
4902 var toAdd = '';
4903 var toRemove = '';
4904 forEach(data, function(status, className) {
4905 var hasClass = !!existing[className];
4906 if (status !== hasClass) {
4907 if (status) {
4908 toAdd += (toAdd.length ? ' ' : '') + className;
4909 } else {
4910 toRemove += (toRemove.length ? ' ' : '') + className;
4911 }
4912 }
4913 });
4914
4915 forEach(element, function(elm) {
4916 toAdd && jqLiteAddClass(elm, toAdd);
4917 toRemove && jqLiteRemoveClass(elm, toRemove);
4918 });
4919 postDigestQueue.remove(element);
4920 }
4921 });
4922 postDigestElements.length = 0;
4923 }
4924
4925
4926 function addRemoveClassesPostDigest(element, add, remove) {
4927 var data = postDigestQueue.get(element) || {};
4928
4929 var classesAdded = updateData(data, add, true);
4930 var classesRemoved = updateData(data, remove, false);
4931
4932 if (classesAdded || classesRemoved) {
4933
4934 postDigestQueue.put(element, data);
4935 postDigestElements.push(element);
4936
4937 if (postDigestElements.length === 1) {
4938 $rootScope.$$postDigest(handleCSSClassChanges);
4939 }
4940 }
4941 }
4942 }];
4943};
4944
4945/**
4946 * @ngdoc provider
4947 * @name $animateProvider
4948 *
4949 * @description
4950 * Default implementation of $animate that doesn't perform any animations, instead just
4951 * synchronously performs DOM updates and resolves the returned runner promise.
4952 *
4953 * In order to enable animations the `ngAnimate` module has to be loaded.
4954 *
4955 * To see the functional implementation check out `src/ngAnimate/animate.js`.
4956 */
4957var $AnimateProvider = ['$provide', function($provide) {
4958 var provider = this;
4959
4960 this.$$registeredAnimations = Object.create(null);
4961
4962 /**
4963 * @ngdoc method
4964 * @name $animateProvider#register
4965 *
4966 * @description
4967 * Registers a new injectable animation factory function. The factory function produces the
4968 * animation object which contains callback functions for each event that is expected to be
4969 * animated.
4970 *
4971 * * `eventFn`: `function(element, ... , doneFunction, options)`
4972 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
4973 * on the type of animation additional arguments will be injected into the animation function. The
4974 * list below explains the function signatures for the different animation methods:
4975 *
4976 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
4977 * - addClass: function(element, addedClasses, doneFunction, options)
4978 * - removeClass: function(element, removedClasses, doneFunction, options)
4979 * - enter, leave, move: function(element, doneFunction, options)
4980 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
4981 *
4982 * Make sure to trigger the `doneFunction` once the animation is fully complete.
4983 *
4984 * ```js
4985 * return {
4986 * //enter, leave, move signature
4987 * eventFn : function(element, done, options) {
4988 * //code to run the animation
4989 * //once complete, then run done()
4990 * return function endFunction(wasCancelled) {
4991 * //code to cancel the animation
4992 * }
4993 * }
4994 * }
4995 * ```
4996 *
4997 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
4998 * @param {Function} factory The factory function that will be executed to return the animation
4999 * object.
5000 */
5001 this.register = function(name, factory) {
5002 if (name && name.charAt(0) !== '.') {
5003 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5004 }
5005
5006 var key = name + '-animation';
5007 provider.$$registeredAnimations[name.substr(1)] = key;
5008 $provide.factory(key, factory);
5009 };
5010
5011 /**
5012 * @ngdoc method
5013 * @name $animateProvider#classNameFilter
5014 *
5015 * @description
5016 * Sets and/or returns the CSS class regular expression that is checked when performing
5017 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5018 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5019 * When setting the `classNameFilter` value, animations will only be performed on elements
5020 * that successfully match the filter expression. This in turn can boost performance
5021 * for low-powered devices as well as applications containing a lot of structural operations.
5022 * @param {RegExp=} expression The className expression which will be checked against all animations
5023 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5024 */
5025 this.classNameFilter = function(expression) {
5026 if (arguments.length === 1) {
5027 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5028 if (this.$$classNameFilter) {
5029 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5030 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5031 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5032
5033 }
5034 }
5035 }
5036 return this.$$classNameFilter;
5037 };
5038
5039 this.$get = ['$$animateQueue', function($$animateQueue) {
5040 function domInsert(element, parentElement, afterElement) {
5041 // if for some reason the previous element was removed
5042 // from the dom sometime before this code runs then let's
5043 // just stick to using the parent element as the anchor
5044 if (afterElement) {
5045 var afterNode = extractElementNode(afterElement);
5046 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5047 afterElement = null;
5048 }
5049 }
5050 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5051 }
5052
5053 /**
5054 * @ngdoc service
5055 * @name $animate
5056 * @description The $animate service exposes a series of DOM utility methods that provide support
5057 * for animation hooks. The default behavior is the application of DOM operations, however,
5058 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5059 * to ensure that animation runs with the triggered DOM operation.
5060 *
5061 * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
5062 * included and only when it is active then the animation hooks that `$animate` triggers will be
5063 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5064 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5065 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5066 *
5067 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5068 *
5069 * To learn more about enabling animation support, click here to visit the
5070 * {@link ngAnimate ngAnimate module page}.
5071 */
5072 return {
5073 // we don't call it directly since non-existant arguments may
5074 // be interpreted as null within the sub enabled function
5075
5076 /**
5077 *
5078 * @ngdoc method
5079 * @name $animate#on
5080 * @kind function
5081 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5082 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5083 * is fired with the following params:
5084 *
5085 * ```js
5086 * $animate.on('enter', container,
5087 * function callback(element, phase) {
5088 * // cool we detected an enter animation within the container
5089 * }
5090 * );
5091 * ```
5092 *
5093 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5094 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5095 * as well as among its children
5096 * @param {Function} callback the callback function that will be fired when the listener is triggered
5097 *
5098 * The arguments present in the callback function are:
5099 * * `element` - The captured DOM element that the animation was fired on.
5100 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5101 */
5102 on: $$animateQueue.on,
5103
5104 /**
5105 *
5106 * @ngdoc method
5107 * @name $animate#off
5108 * @kind function
5109 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5110 * can be used in three different ways depending on the arguments:
5111 *
5112 * ```js
5113 * // remove all the animation event listeners listening for `enter`
5114 * $animate.off('enter');
5115 *
5116 * // remove all the animation event listeners listening for `enter` on the given element and its children
5117 * $animate.off('enter', container);
5118 *
5119 * // remove the event listener function provided by `listenerFn` that is set
5120 * // to listen for `enter` on the given `element` as well as its children
5121 * $animate.off('enter', container, callback);
5122 * ```
5123 *
5124 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5125 * @param {DOMElement=} container the container element the event listener was placed on
5126 * @param {Function=} callback the callback function that was registered as the listener
5127 */
5128 off: $$animateQueue.off,
5129
5130 /**
5131 * @ngdoc method
5132 * @name $animate#pin
5133 * @kind function
5134 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5135 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5136 * element despite being outside the realm of the application or within another application. Say for example if the application
5137 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5138 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5139 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5140 *
5141 * Note that this feature is only active when the `ngAnimate` module is used.
5142 *
5143 * @param {DOMElement} element the external element that will be pinned
5144 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5145 */
5146 pin: $$animateQueue.pin,
5147
5148 /**
5149 *
5150 * @ngdoc method
5151 * @name $animate#enabled
5152 * @kind function
5153 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5154 * function can be called in four ways:
5155 *
5156 * ```js
5157 * // returns true or false
5158 * $animate.enabled();
5159 *
5160 * // changes the enabled state for all animations
5161 * $animate.enabled(false);
5162 * $animate.enabled(true);
5163 *
5164 * // returns true or false if animations are enabled for an element
5165 * $animate.enabled(element);
5166 *
5167 * // changes the enabled state for an element and its children
5168 * $animate.enabled(element, true);
5169 * $animate.enabled(element, false);
5170 * ```
5171 *
5172 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5173 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5174 *
5175 * @return {boolean} whether or not animations are enabled
5176 */
5177 enabled: $$animateQueue.enabled,
5178
5179 /**
5180 * @ngdoc method
5181 * @name $animate#cancel
5182 * @kind function
5183 * @description Cancels the provided animation.
5184 *
5185 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5186 */
5187 cancel: function(runner) {
5188 runner.end && runner.end();
5189 },
5190
5191 /**
5192 *
5193 * @ngdoc method
5194 * @name $animate#enter
5195 * @kind function
5196 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5197 * as the first child within the `parent` element and then triggers an animation.
5198 * A promise is returned that will be resolved during the next digest once the animation
5199 * has completed.
5200 *
5201 * @param {DOMElement} element the element which will be inserted into the DOM
5202 * @param {DOMElement} parent the parent element which will append the element as
5203 * a child (so long as the after element is not present)
5204 * @param {DOMElement=} after the sibling element after which the element will be appended
5205 * @param {object=} options an optional collection of options/styles that will be applied to the element
5206 *
5207 * @return {Promise} the animation callback promise
5208 */
5209 enter: function(element, parent, after, options) {
5210 parent = parent && jqLite(parent);
5211 after = after && jqLite(after);
5212 parent = parent || after.parent();
5213 domInsert(element, parent, after);
5214 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5215 },
5216
5217 /**
5218 *
5219 * @ngdoc method
5220 * @name $animate#move
5221 * @kind function
5222 * @description Inserts (moves) the element into its new position in the DOM either after
5223 * the `after` element (if provided) or as the first child within the `parent` element
5224 * and then triggers an animation. A promise is returned that will be resolved
5225 * during the next digest once the animation has completed.
5226 *
5227 * @param {DOMElement} element the element which will be moved into the new DOM position
5228 * @param {DOMElement} parent the parent element which will append the element as
5229 * a child (so long as the after element is not present)
5230 * @param {DOMElement=} after the sibling element after which the element will be appended
5231 * @param {object=} options an optional collection of options/styles that will be applied to the element
5232 *
5233 * @return {Promise} the animation callback promise
5234 */
5235 move: function(element, parent, after, options) {
5236 parent = parent && jqLite(parent);
5237 after = after && jqLite(after);
5238 parent = parent || after.parent();
5239 domInsert(element, parent, after);
5240 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5241 },
5242
5243 /**
5244 * @ngdoc method
5245 * @name $animate#leave
5246 * @kind function
5247 * @description Triggers an animation and then removes the element from the DOM.
5248 * When the function is called a promise is returned that will be resolved during the next
5249 * digest once the animation has completed.
5250 *
5251 * @param {DOMElement} element the element which will be removed from the DOM
5252 * @param {object=} options an optional collection of options/styles that will be applied to the element
5253 *
5254 * @return {Promise} the animation callback promise
5255 */
5256 leave: function(element, options) {
5257 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5258 element.remove();
5259 });
5260 },
5261
5262 /**
5263 * @ngdoc method
5264 * @name $animate#addClass
5265 * @kind function
5266 *
5267 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5268 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5269 * animation if element already contains the CSS class or if the class is removed at a later step.
5270 * Note that class-based animations are treated differently compared to structural animations
5271 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5272 * depending if CSS or JavaScript animations are used.
5273 *
5274 * @param {DOMElement} element the element which the CSS classes will be applied to
5275 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
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 addClass: function(element, className, options) {
5281 options = prepareAnimateOptions(options);
5282 options.addClass = mergeClasses(options.addclass, className);
5283 return $$animateQueue.push(element, 'addClass', options);
5284 },
5285
5286 /**
5287 * @ngdoc method
5288 * @name $animate#removeClass
5289 * @kind function
5290 *
5291 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5292 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5293 * animation if element does not contain the CSS class or if the class is added at a later step.
5294 * Note that class-based animations are treated differently compared to structural animations
5295 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5296 * depending if CSS or JavaScript animations are used.
5297 *
5298 * @param {DOMElement} element the element which the CSS classes will be applied to
5299 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5300 * @param {object=} options an optional collection of options/styles that will be applied to the element
5301 *
5302 * @return {Promise} the animation callback promise
5303 */
5304 removeClass: function(element, className, options) {
5305 options = prepareAnimateOptions(options);
5306 options.removeClass = mergeClasses(options.removeClass, className);
5307 return $$animateQueue.push(element, 'removeClass', options);
5308 },
5309
5310 /**
5311 * @ngdoc method
5312 * @name $animate#setClass
5313 * @kind function
5314 *
5315 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5316 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5317 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5318 * passed. Note that class-based animations are treated differently compared to structural animations
5319 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5320 * depending if CSS or JavaScript animations are used.
5321 *
5322 * @param {DOMElement} element the element which the CSS classes will be applied to
5323 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5324 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5325 * @param {object=} options an optional collection of options/styles that will be applied to the element
5326 *
5327 * @return {Promise} the animation callback promise
5328 */
5329 setClass: function(element, add, remove, options) {
5330 options = prepareAnimateOptions(options);
5331 options.addClass = mergeClasses(options.addClass, add);
5332 options.removeClass = mergeClasses(options.removeClass, remove);
5333 return $$animateQueue.push(element, 'setClass', options);
5334 },
5335
5336 /**
5337 * @ngdoc method
5338 * @name $animate#animate
5339 * @kind function
5340 *
5341 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5342 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5343 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5344 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5345 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5346 *
5347 * @param {DOMElement} element the element which the CSS styles will be applied to
5348 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5349 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5350 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5351 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5352 * (Note that if no animation is detected then this value will not be appplied to the element.)
5353 * @param {object=} options an optional collection of options/styles that will be applied to the element
5354 *
5355 * @return {Promise} the animation callback promise
5356 */
5357 animate: function(element, from, to, className, options) {
5358 options = prepareAnimateOptions(options);
5359 options.from = options.from ? extend(options.from, from) : from;
5360 options.to = options.to ? extend(options.to, to) : to;
5361
5362 className = className || 'ng-inline-animate';
5363 options.tempClasses = mergeClasses(options.tempClasses, className);
5364 return $$animateQueue.push(element, 'animate', options);
5365 }
5366 };
5367 }];
5368}];
5369
5370/**
5371 * @ngdoc service
5372 * @name $animateCss
5373 * @kind object
5374 *
5375 * @description
5376 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5377 * then the `$animateCss` service will actually perform animations.
5378 *
5379 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5380 */
5381var $CoreAnimateCssProvider = function() {
5382 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5383
5384 var RAFPromise = function() {};
5385 RAFPromise.prototype = {
5386 done: function(cancel) {
5387 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5388 },
5389 end: function() {
5390 this.done();
5391 },
5392 cancel: function() {
5393 this.done(true);
5394 },
5395 getPromise: function() {
5396 if (!this.defer) {
5397 this.defer = $q.defer();
5398 }
5399 return this.defer.promise;
5400 },
5401 then: function(f1,f2) {
5402 return this.getPromise().then(f1,f2);
5403 },
5404 'catch': function(f1) {
5405 return this.getPromise()['catch'](f1);
5406 },
5407 'finally': function(f1) {
5408 return this.getPromise()['finally'](f1);
5409 }
5410 };
5411
5412 return function(element, options) {
5413 // there is no point in applying the styles since
5414 // there is no animation that goes on at all in
5415 // this version of $animateCss.
5416 if (options.cleanupStyles) {
5417 options.from = options.to = null;
5418 }
5419
5420 if (options.from) {
5421 element.css(options.from);
5422 options.from = null;
5423 }
5424
5425 var closed, runner = new RAFPromise();
5426 return {
5427 start: run,
5428 end: run
5429 };
5430
5431 function run() {
5432 $$rAF(function() {
5433 close();
5434 if (!closed) {
5435 runner.done();
5436 }
5437 closed = true;
5438 });
5439 return runner;
5440 }
5441
5442 function close() {
5443 if (options.addClass) {
5444 element.addClass(options.addClass);
5445 options.addClass = null;
5446 }
5447 if (options.removeClass) {
5448 element.removeClass(options.removeClass);
5449 options.removeClass = null;
5450 }
5451 if (options.to) {
5452 element.css(options.to);
5453 options.to = null;
5454 }
5455 }
5456 };
5457 }];
5458};
5459
5460/* global stripHash: true */
5461
5462/**
5463 * ! This is a private undocumented service !
5464 *
5465 * @name $browser
5466 * @requires $log
5467 * @description
5468 * This object has two goals:
5469 *
5470 * - hide all the global state in the browser caused by the window object
5471 * - abstract away all the browser specific features and inconsistencies
5472 *
5473 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5474 * service, which can be used for convenient testing of the application without the interaction with
5475 * the real browser apis.
5476 */
5477/**
5478 * @param {object} window The global window object.
5479 * @param {object} document jQuery wrapped document.
5480 * @param {object} $log window.console or an object with the same interface.
5481 * @param {object} $sniffer $sniffer service
5482 */
5483function Browser(window, document, $log, $sniffer) {
5484 var self = this,
5485 rawDocument = document[0],
5486 location = window.location,
5487 history = window.history,
5488 setTimeout = window.setTimeout,
5489 clearTimeout = window.clearTimeout,
5490 pendingDeferIds = {};
5491
5492 self.isMock = false;
5493
5494 var outstandingRequestCount = 0;
5495 var outstandingRequestCallbacks = [];
5496
5497 // TODO(vojta): remove this temporary api
5498 self.$$completeOutstandingRequest = completeOutstandingRequest;
5499 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5500
5501 /**
5502 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5503 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5504 */
5505 function completeOutstandingRequest(fn) {
5506 try {
5507 fn.apply(null, sliceArgs(arguments, 1));
5508 } finally {
5509 outstandingRequestCount--;
5510 if (outstandingRequestCount === 0) {
5511 while (outstandingRequestCallbacks.length) {
5512 try {
5513 outstandingRequestCallbacks.pop()();
5514 } catch (e) {
5515 $log.error(e);
5516 }
5517 }
5518 }
5519 }
5520 }
5521
5522 function getHash(url) {
5523 var index = url.indexOf('#');
5524 return index === -1 ? '' : url.substr(index);
5525 }
5526
5527 /**
5528 * @private
5529 * Note: this method is used only by scenario runner
5530 * TODO(vojta): prefix this method with $$ ?
5531 * @param {function()} callback Function that will be called when no outstanding request
5532 */
5533 self.notifyWhenNoOutstandingRequests = function(callback) {
5534 if (outstandingRequestCount === 0) {
5535 callback();
5536 } else {
5537 outstandingRequestCallbacks.push(callback);
5538 }
5539 };
5540
5541 //////////////////////////////////////////////////////////////
5542 // URL API
5543 //////////////////////////////////////////////////////////////
5544
5545 var cachedState, lastHistoryState,
5546 lastBrowserUrl = location.href,
5547 baseElement = document.find('base'),
5548 pendingLocation = null;
5549
5550 cacheState();
5551 lastHistoryState = cachedState;
5552
5553 /**
5554 * @name $browser#url
5555 *
5556 * @description
5557 * GETTER:
5558 * Without any argument, this method just returns current value of location.href.
5559 *
5560 * SETTER:
5561 * With at least one argument, this method sets url to new value.
5562 * If html5 history api supported, pushState/replaceState is used, otherwise
5563 * location.href/location.replace is used.
5564 * Returns its own instance to allow chaining
5565 *
5566 * NOTE: this api is intended for use only by the $location service. Please use the
5567 * {@link ng.$location $location service} to change url.
5568 *
5569 * @param {string} url New url (when used as setter)
5570 * @param {boolean=} replace Should new url replace current history record?
5571 * @param {object=} state object to use with pushState/replaceState
5572 */
5573 self.url = function(url, replace, state) {
5574 // In modern browsers `history.state` is `null` by default; treating it separately
5575 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5576 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5577 if (isUndefined(state)) {
5578 state = null;
5579 }
5580
5581 // Android Browser BFCache causes location, history reference to become stale.
5582 if (location !== window.location) location = window.location;
5583 if (history !== window.history) history = window.history;
5584
5585 // setter
5586 if (url) {
5587 var sameState = lastHistoryState === state;
5588
5589 // Don't change anything if previous and current URLs and states match. This also prevents
5590 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5591 // See https://github.com/angular/angular.js/commit/ffb2701
5592 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5593 return self;
5594 }
5595 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5596 lastBrowserUrl = url;
5597 lastHistoryState = state;
5598 // Don't use history API if only the hash changed
5599 // due to a bug in IE10/IE11 which leads
5600 // to not firing a `hashchange` nor `popstate` event
5601 // in some cases (see #9143).
5602 if ($sniffer.history && (!sameBase || !sameState)) {
5603 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5604 cacheState();
5605 // Do the assignment again so that those two variables are referentially identical.
5606 lastHistoryState = cachedState;
5607 } else {
5608 if (!sameBase || pendingLocation) {
5609 pendingLocation = url;
5610 }
5611 if (replace) {
5612 location.replace(url);
5613 } else if (!sameBase) {
5614 location.href = url;
5615 } else {
5616 location.hash = getHash(url);
5617 }
5618 if (location.href !== url) {
5619 pendingLocation = url;
5620 }
5621 }
5622 return self;
5623 // getter
5624 } else {
5625 // - pendingLocation is needed as browsers don't allow to read out
5626 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5627 // https://openradar.appspot.com/22186109).
5628 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5629 return pendingLocation || location.href.replace(/%27/g,"'");
5630 }
5631 };
5632
5633 /**
5634 * @name $browser#state
5635 *
5636 * @description
5637 * This method is a getter.
5638 *
5639 * Return history.state or null if history.state is undefined.
5640 *
5641 * @returns {object} state
5642 */
5643 self.state = function() {
5644 return cachedState;
5645 };
5646
5647 var urlChangeListeners = [],
5648 urlChangeInit = false;
5649
5650 function cacheStateAndFireUrlChange() {
5651 pendingLocation = null;
5652 cacheState();
5653 fireUrlChange();
5654 }
5655
5656 function getCurrentState() {
5657 try {
5658 return history.state;
5659 } catch (e) {
5660 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5661 }
5662 }
5663
5664 // This variable should be used *only* inside the cacheState function.
5665 var lastCachedState = null;
5666 function cacheState() {
5667 // This should be the only place in $browser where `history.state` is read.
5668 cachedState = getCurrentState();
5669 cachedState = isUndefined(cachedState) ? null : cachedState;
5670
5671 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5672 if (equals(cachedState, lastCachedState)) {
5673 cachedState = lastCachedState;
5674 }
5675 lastCachedState = cachedState;
5676 }
5677
5678 function fireUrlChange() {
5679 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5680 return;
5681 }
5682
5683 lastBrowserUrl = self.url();
5684 lastHistoryState = cachedState;
5685 forEach(urlChangeListeners, function(listener) {
5686 listener(self.url(), cachedState);
5687 });
5688 }
5689
5690 /**
5691 * @name $browser#onUrlChange
5692 *
5693 * @description
5694 * Register callback function that will be called, when url changes.
5695 *
5696 * It's only called when the url is changed from outside of angular:
5697 * - user types different url into address bar
5698 * - user clicks on history (forward/back) button
5699 * - user clicks on a link
5700 *
5701 * It's not called when url is changed by $browser.url() method
5702 *
5703 * The listener gets called with new url as parameter.
5704 *
5705 * NOTE: this api is intended for use only by the $location service. Please use the
5706 * {@link ng.$location $location service} to monitor url changes in angular apps.
5707 *
5708 * @param {function(string)} listener Listener function to be called when url changes.
5709 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5710 */
5711 self.onUrlChange = function(callback) {
5712 // TODO(vojta): refactor to use node's syntax for events
5713 if (!urlChangeInit) {
5714 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5715 // don't fire popstate when user change the address bar and don't fire hashchange when url
5716 // changed by push/replaceState
5717
5718 // html5 history api - popstate event
5719 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5720 // hashchange event
5721 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5722
5723 urlChangeInit = true;
5724 }
5725
5726 urlChangeListeners.push(callback);
5727 return callback;
5728 };
5729
5730 /**
5731 * @private
5732 * Remove popstate and hashchange handler from window.
5733 *
5734 * NOTE: this api is intended for use only by $rootScope.
5735 */
5736 self.$$applicationDestroyed = function() {
5737 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5738 };
5739
5740 /**
5741 * Checks whether the url has changed outside of Angular.
5742 * Needs to be exported to be able to check for changes that have been done in sync,
5743 * as hashchange/popstate events fire in async.
5744 */
5745 self.$$checkUrlChange = fireUrlChange;
5746
5747 //////////////////////////////////////////////////////////////
5748 // Misc API
5749 //////////////////////////////////////////////////////////////
5750
5751 /**
5752 * @name $browser#baseHref
5753 *
5754 * @description
5755 * Returns current <base href>
5756 * (always relative - without domain)
5757 *
5758 * @returns {string} The current base href
5759 */
5760 self.baseHref = function() {
5761 var href = baseElement.attr('href');
5762 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5763 };
5764
5765 /**
5766 * @name $browser#defer
5767 * @param {function()} fn A function, who's execution should be deferred.
5768 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5769 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5770 *
5771 * @description
5772 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5773 *
5774 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5775 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5776 * via `$browser.defer.flush()`.
5777 *
5778 */
5779 self.defer = function(fn, delay) {
5780 var timeoutId;
5781 outstandingRequestCount++;
5782 timeoutId = setTimeout(function() {
5783 delete pendingDeferIds[timeoutId];
5784 completeOutstandingRequest(fn);
5785 }, delay || 0);
5786 pendingDeferIds[timeoutId] = true;
5787 return timeoutId;
5788 };
5789
5790
5791 /**
5792 * @name $browser#defer.cancel
5793 *
5794 * @description
5795 * Cancels a deferred task identified with `deferId`.
5796 *
5797 * @param {*} deferId Token returned by the `$browser.defer` function.
5798 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5799 * canceled.
5800 */
5801 self.defer.cancel = function(deferId) {
5802 if (pendingDeferIds[deferId]) {
5803 delete pendingDeferIds[deferId];
5804 clearTimeout(deferId);
5805 completeOutstandingRequest(noop);
5806 return true;
5807 }
5808 return false;
5809 };
5810
5811}
5812
5813function $BrowserProvider() {
5814 this.$get = ['$window', '$log', '$sniffer', '$document',
5815 function($window, $log, $sniffer, $document) {
5816 return new Browser($window, $document, $log, $sniffer);
5817 }];
5818}
5819
5820/**
5821 * @ngdoc service
5822 * @name $cacheFactory
5823 *
5824 * @description
5825 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5826 * them.
5827 *
5828 * ```js
5829 *
5830 * var cache = $cacheFactory('cacheId');
5831 * expect($cacheFactory.get('cacheId')).toBe(cache);
5832 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5833 *
5834 * cache.put("key", "value");
5835 * cache.put("another key", "another value");
5836 *
5837 * // We've specified no options on creation
5838 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5839 *
5840 * ```
5841 *
5842 *
5843 * @param {string} cacheId Name or id of the newly created cache.
5844 * @param {object=} options Options object that specifies the cache behavior. Properties:
5845 *
5846 * - `{number=}` `capacity` — turns the cache into LRU cache.
5847 *
5848 * @returns {object} Newly created cache object with the following set of methods:
5849 *
5850 * - `{object}` `info()` — Returns id, size, and options of cache.
5851 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5852 * it.
5853 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5854 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5855 * - `{void}` `removeAll()` — Removes all cached values.
5856 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5857 *
5858 * @example
5859 <example module="cacheExampleApp">
5860 <file name="index.html">
5861 <div ng-controller="CacheController">
5862 <input ng-model="newCacheKey" placeholder="Key">
5863 <input ng-model="newCacheValue" placeholder="Value">
5864 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5865
5866 <p ng-if="keys.length">Cached Values</p>
5867 <div ng-repeat="key in keys">
5868 <span ng-bind="key"></span>
5869 <span>: </span>
5870 <b ng-bind="cache.get(key)"></b>
5871 </div>
5872
5873 <p>Cache Info</p>
5874 <div ng-repeat="(key, value) in cache.info()">
5875 <span ng-bind="key"></span>
5876 <span>: </span>
5877 <b ng-bind="value"></b>
5878 </div>
5879 </div>
5880 </file>
5881 <file name="script.js">
5882 angular.module('cacheExampleApp', []).
5883 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
5884 $scope.keys = [];
5885 $scope.cache = $cacheFactory('cacheId');
5886 $scope.put = function(key, value) {
5887 if (angular.isUndefined($scope.cache.get(key))) {
5888 $scope.keys.push(key);
5889 }
5890 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
5891 };
5892 }]);
5893 </file>
5894 <file name="style.css">
5895 p {
5896 margin: 10px 0 3px;
5897 }
5898 </file>
5899 </example>
5900 */
5901function $CacheFactoryProvider() {
5902
5903 this.$get = function() {
5904 var caches = {};
5905
5906 function cacheFactory(cacheId, options) {
5907 if (cacheId in caches) {
5908 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
5909 }
5910
5911 var size = 0,
5912 stats = extend({}, options, {id: cacheId}),
5913 data = {},
5914 capacity = (options && options.capacity) || Number.MAX_VALUE,
5915 lruHash = {},
5916 freshEnd = null,
5917 staleEnd = null;
5918
5919 /**
5920 * @ngdoc type
5921 * @name $cacheFactory.Cache
5922 *
5923 * @description
5924 * A cache object used to store and retrieve data, primarily used by
5925 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
5926 * templates and other data.
5927 *
5928 * ```js
5929 * angular.module('superCache')
5930 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
5931 * return $cacheFactory('super-cache');
5932 * }]);
5933 * ```
5934 *
5935 * Example test:
5936 *
5937 * ```js
5938 * it('should behave like a cache', inject(function(superCache) {
5939 * superCache.put('key', 'value');
5940 * superCache.put('another key', 'another value');
5941 *
5942 * expect(superCache.info()).toEqual({
5943 * id: 'super-cache',
5944 * size: 2
5945 * });
5946 *
5947 * superCache.remove('another key');
5948 * expect(superCache.get('another key')).toBeUndefined();
5949 *
5950 * superCache.removeAll();
5951 * expect(superCache.info()).toEqual({
5952 * id: 'super-cache',
5953 * size: 0
5954 * });
5955 * }));
5956 * ```
5957 */
5958 return caches[cacheId] = {
5959
5960 /**
5961 * @ngdoc method
5962 * @name $cacheFactory.Cache#put
5963 * @kind function
5964 *
5965 * @description
5966 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
5967 * retrieved later, and incrementing the size of the cache if the key was not already
5968 * present in the cache. If behaving like an LRU cache, it will also remove stale
5969 * entries from the set.
5970 *
5971 * It will not insert undefined values into the cache.
5972 *
5973 * @param {string} key the key under which the cached data is stored.
5974 * @param {*} value the value to store alongside the key. If it is undefined, the key
5975 * will not be stored.
5976 * @returns {*} the value stored.
5977 */
5978 put: function(key, value) {
5979 if (isUndefined(value)) return;
5980 if (capacity < Number.MAX_VALUE) {
5981 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
5982
5983 refresh(lruEntry);
5984 }
5985
5986 if (!(key in data)) size++;
5987 data[key] = value;
5988
5989 if (size > capacity) {
5990 this.remove(staleEnd.key);
5991 }
5992
5993 return value;
5994 },
5995
5996 /**
5997 * @ngdoc method
5998 * @name $cacheFactory.Cache#get
5999 * @kind function
6000 *
6001 * @description
6002 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6003 *
6004 * @param {string} key the key of the data to be retrieved
6005 * @returns {*} the value stored.
6006 */
6007 get: function(key) {
6008 if (capacity < Number.MAX_VALUE) {
6009 var lruEntry = lruHash[key];
6010
6011 if (!lruEntry) return;
6012
6013 refresh(lruEntry);
6014 }
6015
6016 return data[key];
6017 },
6018
6019
6020 /**
6021 * @ngdoc method
6022 * @name $cacheFactory.Cache#remove
6023 * @kind function
6024 *
6025 * @description
6026 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6027 *
6028 * @param {string} key the key of the entry to be removed
6029 */
6030 remove: function(key) {
6031 if (capacity < Number.MAX_VALUE) {
6032 var lruEntry = lruHash[key];
6033
6034 if (!lruEntry) return;
6035
6036 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6037 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6038 link(lruEntry.n,lruEntry.p);
6039
6040 delete lruHash[key];
6041 }
6042
6043 delete data[key];
6044 size--;
6045 },
6046
6047
6048 /**
6049 * @ngdoc method
6050 * @name $cacheFactory.Cache#removeAll
6051 * @kind function
6052 *
6053 * @description
6054 * Clears the cache object of any entries.
6055 */
6056 removeAll: function() {
6057 data = {};
6058 size = 0;
6059 lruHash = {};
6060 freshEnd = staleEnd = null;
6061 },
6062
6063
6064 /**
6065 * @ngdoc method
6066 * @name $cacheFactory.Cache#destroy
6067 * @kind function
6068 *
6069 * @description
6070 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6071 * removing it from the {@link $cacheFactory $cacheFactory} set.
6072 */
6073 destroy: function() {
6074 data = null;
6075 stats = null;
6076 lruHash = null;
6077 delete caches[cacheId];
6078 },
6079
6080
6081 /**
6082 * @ngdoc method
6083 * @name $cacheFactory.Cache#info
6084 * @kind function
6085 *
6086 * @description
6087 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6088 *
6089 * @returns {object} an object with the following properties:
6090 * <ul>
6091 * <li>**id**: the id of the cache instance</li>
6092 * <li>**size**: the number of entries kept in the cache instance</li>
6093 * <li>**...**: any additional properties from the options object when creating the
6094 * cache.</li>
6095 * </ul>
6096 */
6097 info: function() {
6098 return extend({}, stats, {size: size});
6099 }
6100 };
6101
6102
6103 /**
6104 * makes the `entry` the freshEnd of the LRU linked list
6105 */
6106 function refresh(entry) {
6107 if (entry != freshEnd) {
6108 if (!staleEnd) {
6109 staleEnd = entry;
6110 } else if (staleEnd == entry) {
6111 staleEnd = entry.n;
6112 }
6113
6114 link(entry.n, entry.p);
6115 link(entry, freshEnd);
6116 freshEnd = entry;
6117 freshEnd.n = null;
6118 }
6119 }
6120
6121
6122 /**
6123 * bidirectionally links two entries of the LRU linked list
6124 */
6125 function link(nextEntry, prevEntry) {
6126 if (nextEntry != prevEntry) {
6127 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6128 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6129 }
6130 }
6131 }
6132
6133
6134 /**
6135 * @ngdoc method
6136 * @name $cacheFactory#info
6137 *
6138 * @description
6139 * Get information about all the caches that have been created
6140 *
6141 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6142 */
6143 cacheFactory.info = function() {
6144 var info = {};
6145 forEach(caches, function(cache, cacheId) {
6146 info[cacheId] = cache.info();
6147 });
6148 return info;
6149 };
6150
6151
6152 /**
6153 * @ngdoc method
6154 * @name $cacheFactory#get
6155 *
6156 * @description
6157 * Get access to a cache object by the `cacheId` used when it was created.
6158 *
6159 * @param {string} cacheId Name or id of a cache to access.
6160 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6161 */
6162 cacheFactory.get = function(cacheId) {
6163 return caches[cacheId];
6164 };
6165
6166
6167 return cacheFactory;
6168 };
6169}
6170
6171/**
6172 * @ngdoc service
6173 * @name $templateCache
6174 *
6175 * @description
6176 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6177 * can load templates directly into the cache in a `script` tag, or by consuming the
6178 * `$templateCache` service directly.
6179 *
6180 * Adding via the `script` tag:
6181 *
6182 * ```html
6183 * <script type="text/ng-template" id="templateId.html">
6184 * <p>This is the content of the template</p>
6185 * </script>
6186 * ```
6187 *
6188 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6189 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6190 * element with ng-app attribute), otherwise the template will be ignored.
6191 *
6192 * Adding via the `$templateCache` service:
6193 *
6194 * ```js
6195 * var myApp = angular.module('myApp', []);
6196 * myApp.run(function($templateCache) {
6197 * $templateCache.put('templateId.html', 'This is the content of the template');
6198 * });
6199 * ```
6200 *
6201 * To retrieve the template later, simply use it in your HTML:
6202 * ```html
6203 * <div ng-include=" 'templateId.html' "></div>
6204 * ```
6205 *
6206 * or get it via Javascript:
6207 * ```js
6208 * $templateCache.get('templateId.html')
6209 * ```
6210 *
6211 * See {@link ng.$cacheFactory $cacheFactory}.
6212 *
6213 */
6214function $TemplateCacheProvider() {
6215 this.$get = ['$cacheFactory', function($cacheFactory) {
6216 return $cacheFactory('templates');
6217 }];
6218}
6219
6220/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6221 * Any commits to this file should be reviewed with security in mind. *
6222 * Changes to this file can potentially create security vulnerabilities. *
6223 * An approval from 2 Core members with history of modifying *
6224 * this file is required. *
6225 * *
6226 * Does the change somehow allow for arbitrary javascript to be executed? *
6227 * Or allows for someone to change the prototype of built-in objects? *
6228 * Or gives undesired access to variables likes document or window? *
6229 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6230
6231/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6232 *
6233 * DOM-related variables:
6234 *
6235 * - "node" - DOM Node
6236 * - "element" - DOM Element or Node
6237 * - "$node" or "$element" - jqLite-wrapped node or element
6238 *
6239 *
6240 * Compiler related stuff:
6241 *
6242 * - "linkFn" - linking fn of a single directive
6243 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6244 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6245 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6246 */
6247
6248
6249/**
6250 * @ngdoc service
6251 * @name $compile
6252 * @kind function
6253 *
6254 * @description
6255 * Compiles an HTML string or DOM into a template and produces a template function, which
6256 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6257 *
6258 * The compilation is a process of walking the DOM tree and matching DOM elements to
6259 * {@link ng.$compileProvider#directive directives}.
6260 *
6261 * <div class="alert alert-warning">
6262 * **Note:** This document is an in-depth reference of all directive options.
6263 * For a gentle introduction to directives with examples of common use cases,
6264 * see the {@link guide/directive directive guide}.
6265 * </div>
6266 *
6267 * ## Comprehensive Directive API
6268 *
6269 * There are many different options for a directive.
6270 *
6271 * The difference resides in the return value of the factory function.
6272 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6273 * or just the `postLink` function (all other properties will have the default values).
6274 *
6275 * <div class="alert alert-success">
6276 * **Best Practice:** It's recommended to use the "directive definition object" form.
6277 * </div>
6278 *
6279 * Here's an example directive declared with a Directive Definition Object:
6280 *
6281 * ```js
6282 * var myModule = angular.module(...);
6283 *
6284 * myModule.directive('directiveName', function factory(injectables) {
6285 * var directiveDefinitionObject = {
6286 * priority: 0,
6287 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6288 * // or
6289 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6290 * transclude: false,
6291 * restrict: 'A',
6292 * templateNamespace: 'html',
6293 * scope: false,
6294 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6295 * controllerAs: 'stringIdentifier',
6296 * bindToController: false,
6297 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6298 * compile: function compile(tElement, tAttrs, transclude) {
6299 * return {
6300 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6301 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6302 * }
6303 * // or
6304 * // return function postLink( ... ) { ... }
6305 * },
6306 * // or
6307 * // link: {
6308 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6309 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6310 * // }
6311 * // or
6312 * // link: function postLink( ... ) { ... }
6313 * };
6314 * return directiveDefinitionObject;
6315 * });
6316 * ```
6317 *
6318 * <div class="alert alert-warning">
6319 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6320 * </div>
6321 *
6322 * Therefore the above can be simplified as:
6323 *
6324 * ```js
6325 * var myModule = angular.module(...);
6326 *
6327 * myModule.directive('directiveName', function factory(injectables) {
6328 * var directiveDefinitionObject = {
6329 * link: function postLink(scope, iElement, iAttrs) { ... }
6330 * };
6331 * return directiveDefinitionObject;
6332 * // or
6333 * // return function postLink(scope, iElement, iAttrs) { ... }
6334 * });
6335 * ```
6336 *
6337 *
6338 *
6339 * ### Directive Definition Object
6340 *
6341 * The directive definition object provides instructions to the {@link ng.$compile
6342 * compiler}. The attributes are:
6343 *
6344 * #### `multiElement`
6345 * When this property is set to true, the HTML compiler will collect DOM nodes between
6346 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6347 * together as the directive elements. It is recommended that this feature be used on directives
6348 * which are not strictly behavioural (such as {@link ngClick}), and which
6349 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6350 *
6351 * #### `priority`
6352 * When there are multiple directives defined on a single DOM element, sometimes it
6353 * is necessary to specify the order in which the directives are applied. The `priority` is used
6354 * to sort the directives before their `compile` functions get called. Priority is defined as a
6355 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6356 * are also run in priority order, but post-link functions are run in reverse order. The order
6357 * of directives with the same priority is undefined. The default priority is `0`.
6358 *
6359 * #### `terminal`
6360 * If set to true then the current `priority` will be the last set of directives
6361 * which will execute (any directives at the current priority will still execute
6362 * as the order of execution on same `priority` is undefined). Note that expressions
6363 * and other directives used in the directive's template will also be excluded from execution.
6364 *
6365 * #### `scope`
6366 * The scope property can be `true`, an object or a falsy value:
6367 *
6368 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6369 *
6370 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6371 * the directive's element. If multiple directives on the same element request a new scope,
6372 * only one new scope is created. The new scope rule does not apply for the root of the template
6373 * since the root of the template always gets a new scope.
6374 *
6375 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6376 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6377 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6378 * data in the parent scope.
6379 *
6380 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6381 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6382 * the object hash map to the name of the property on the isolate scope; the values define how the property
6383 * is bound to the parent scope, via matching attributes on the directive's element:
6384 *
6385 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6386 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6387 * attribute name is assumed to be the same as the local name.
6388 * Given `<widget my-attr="hello {{name}}">` and widget definition
6389 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6390 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6391 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6392 * component scope).
6393 *
6394 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6395 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6396 * name is specified then the attribute name is assumed to be the same as the local name.
6397 * Given `<widget my-attr="parentModel">` and widget definition of
6398 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6399 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6400 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6401 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6402 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6403 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6404 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6405 *
6406 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6407 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6408 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6409 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6410 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6411 * pass data from the isolated scope via an expression to the parent scope, this can be
6412 * done by passing a map of local variable names and values into the expression wrapper fn.
6413 * For example, if the expression is `increment(amount)` then we can specify the amount value
6414 * by calling the `localFn` as `localFn({amount: 22})`.
6415 *
6416 * In general it's possible to apply more than one directive to one element, but there might be limitations
6417 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6418 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6419 *
6420 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6421 * * **child scope** + **no scope** => Both directives will share one single child scope
6422 * * **child scope** + **child scope** => Both directives will share one single child scope
6423 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6424 * its parent's scope
6425 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6426 * be applied to the same element.
6427 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6428 * cannot be applied to the same element.
6429 *
6430 *
6431 * #### `bindToController`
6432 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6433 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6434 * is instantiated, the initial values of the isolate scope bindings are already available.
6435 *
6436 * #### `controller`
6437 * Controller constructor function. The controller is instantiated before the
6438 * pre-linking phase and can be accessed by other directives (see
6439 * `require` attribute). This allows the directives to communicate with each other and augment
6440 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6441 *
6442 * * `$scope` - Current scope associated with the element
6443 * * `$element` - Current element
6444 * * `$attrs` - Current attributes object for the element
6445 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6446 * `function([scope], cloneLinkingFn, futureParentElement)`.
6447 * * `scope`: optional argument to override the scope.
6448 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6449 * * `futureParentElement`:
6450 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6451 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6452 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6453 * and when the `cloneLinkinFn` is passed,
6454 * as those elements need to created and cloned in a special way when they are defined outside their
6455 * usual containers (e.g. like `<svg>`).
6456 * * See also the `directive.templateNamespace` property.
6457 *
6458 *
6459 * #### `require`
6460 * Require another directive and inject its controller as the fourth argument to the linking function. The
6461 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6462 * injected argument will be an array in corresponding order. If no such directive can be
6463 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6464 * is specified, in which case error checking is skipped). The name can be prefixed with:
6465 *
6466 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6467 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6468 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6469 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6470 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6471 * `null` to the `link` fn if not found.
6472 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6473 * `null` to the `link` fn if not found.
6474 *
6475 *
6476 * #### `controllerAs`
6477 * Identifier name for a reference to the controller in the directive's scope.
6478 * This allows the controller to be referenced from the directive template. This is especially
6479 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6480 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6481 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6482 *
6483 *
6484 * #### `restrict`
6485 * String of subset of `EACM` which restricts the directive to a specific directive
6486 * declaration style. If omitted, the defaults (elements and attributes) are used.
6487 *
6488 * * `E` - Element name (default): `<my-directive></my-directive>`
6489 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6490 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6491 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6492 *
6493 *
6494 * #### `templateNamespace`
6495 * String representing the document type used by the markup in the template.
6496 * AngularJS needs this information as those elements need to be created and cloned
6497 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6498 *
6499 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6500 * top-level elements such as `<svg>` or `<math>`.
6501 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6502 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6503 *
6504 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6505 *
6506 * #### `template`
6507 * HTML markup that may:
6508 * * Replace the contents of the directive's element (default).
6509 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6510 * * Wrap the contents of the directive's element (if `transclude` is true).
6511 *
6512 * Value may be:
6513 *
6514 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6515 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6516 * function api below) and returns a string value.
6517 *
6518 *
6519 * #### `templateUrl`
6520 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6521 *
6522 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6523 * for later when the template has been resolved. In the meantime it will continue to compile and link
6524 * sibling and parent elements as though this element had not contained any directives.
6525 *
6526 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6527 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6528 * case when only one deeply nested directive has `templateUrl`.
6529 *
6530 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6531 *
6532 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6533 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6534 * a string value representing the url. In either case, the template URL is passed through {@link
6535 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6536 *
6537 *
6538 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6539 * specify what the template should replace. Defaults to `false`.
6540 *
6541 * * `true` - the template will replace the directive's element.
6542 * * `false` - the template will replace the contents of the directive's element.
6543 *
6544 * The replacement process migrates all of the attributes / classes from the old element to the new
6545 * one. See the {@link guide/directive#template-expanding-directive
6546 * Directives Guide} for an example.
6547 *
6548 * There are very few scenarios where element replacement is required for the application function,
6549 * the main one being reusable custom components that are used within SVG contexts
6550 * (because SVG doesn't work with custom elements in the DOM tree).
6551 *
6552 * #### `transclude`
6553 * Extract the contents of the element where the directive appears and make it available to the directive.
6554 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6555 * {@link $compile#transclusion Transclusion} section below.
6556 *
6557 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6558 * directive's element or the entire element:
6559 *
6560 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6561 * * `'element'` - transclude the whole of the directive's element including any directives on this
6562 * element that defined at a lower priority than this directive. When used, the `template`
6563 * property is ignored.
6564 *
6565 *
6566 * #### `compile`
6567 *
6568 * ```js
6569 * function compile(tElement, tAttrs, transclude) { ... }
6570 * ```
6571 *
6572 * The compile function deals with transforming the template DOM. Since most directives do not do
6573 * template transformation, it is not used often. The compile function takes the following arguments:
6574 *
6575 * * `tElement` - template element - The element where the directive has been declared. It is
6576 * safe to do template transformation on the element and child elements only.
6577 *
6578 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6579 * between all directive compile functions.
6580 *
6581 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6582 *
6583 * <div class="alert alert-warning">
6584 * **Note:** The template instance and the link instance may be different objects if the template has
6585 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6586 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6587 * should be done in a linking function rather than in a compile function.
6588 * </div>
6589
6590 * <div class="alert alert-warning">
6591 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6592 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6593 * stack overflow errors.
6594 *
6595 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6596 * a directive's template instead of relying on automatic template compilation via `template` or
6597 * `templateUrl` declaration or manual compilation inside the compile function.
6598 * </div>
6599 *
6600 * <div class="alert alert-danger">
6601 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6602 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6603 * to the link function instead.
6604 * </div>
6605
6606 * A compile function can have a return value which can be either a function or an object.
6607 *
6608 * * returning a (post-link) function - is equivalent to registering the linking function via the
6609 * `link` property of the config object when the compile function is empty.
6610 *
6611 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6612 * control when a linking function should be called during the linking phase. See info about
6613 * pre-linking and post-linking functions below.
6614 *
6615 *
6616 * #### `link`
6617 * This property is used only if the `compile` property is not defined.
6618 *
6619 * ```js
6620 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6621 * ```
6622 *
6623 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6624 * executed after the template has been cloned. This is where most of the directive logic will be
6625 * put.
6626 *
6627 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6628 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6629 *
6630 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6631 * manipulate the children of the element only in `postLink` function since the children have
6632 * already been linked.
6633 *
6634 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6635 * between all directive linking functions.
6636 *
6637 * * `controller` - the directive's required controller instance(s) - Instances are shared
6638 * among all directives, which allows the directives to use the controllers as a communication
6639 * channel. The exact value depends on the directive's `require` property:
6640 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6641 * * `string`: the controller instance
6642 * * `array`: array of controller instances
6643 *
6644 * If a required controller cannot be found, and it is optional, the instance is `null`,
6645 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6646 *
6647 * Note that you can also require the directive's own controller - it will be made available like
6648 * any other controller.
6649 *
6650 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6651 * This is the same as the `$transclude`
6652 * parameter of directive controllers, see there for details.
6653 * `function([scope], cloneLinkingFn, futureParentElement)`.
6654 *
6655 * #### Pre-linking function
6656 *
6657 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6658 * compiler linking function will fail to locate the correct elements for linking.
6659 *
6660 * #### Post-linking function
6661 *
6662 * Executed after the child elements are linked.
6663 *
6664 * Note that child elements that contain `templateUrl` directives will not have been compiled
6665 * and linked since they are waiting for their template to load asynchronously and their own
6666 * compilation and linking has been suspended until that occurs.
6667 *
6668 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6669 * for their async templates to be resolved.
6670 *
6671 *
6672 * ### Transclusion
6673 *
6674 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6675 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6676 * scope from where they were taken.
6677 *
6678 * Transclusion is used (often with {@link ngTransclude}) to insert the
6679 * original contents of a directive's element into a specified place in the template of the directive.
6680 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6681 * content has access to the properties on the scope from which it was taken, even if the directive
6682 * has isolated scope.
6683 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6684 *
6685 * This makes it possible for the widget to have private state for its template, while the transcluded
6686 * content has access to its originating scope.
6687 *
6688 * <div class="alert alert-warning">
6689 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6690 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6691 * Testing Transclusion Directives}.
6692 * </div>
6693 *
6694 * #### Transclusion Functions
6695 *
6696 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6697 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6698 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6699 *
6700 * <div class="alert alert-info">
6701 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6702 * ngTransclude will deal with it for us.
6703 * </div>
6704 *
6705 * If you want to manually control the insertion and removal of the transcluded content in your directive
6706 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6707 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6708 *
6709 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6710 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6711 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6712 *
6713 * <div class="alert alert-info">
6714 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6715 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6716 * </div>
6717 *
6718 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6719 * attach function**:
6720 *
6721 * ```js
6722 * var transcludedContent, transclusionScope;
6723 *
6724 * $transclude(function(clone, scope) {
6725 * element.append(clone);
6726 * transcludedContent = clone;
6727 * transclusionScope = scope;
6728 * });
6729 * ```
6730 *
6731 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6732 * associated transclusion scope:
6733 *
6734 * ```js
6735 * transcludedContent.remove();
6736 * transclusionScope.$destroy();
6737 * ```
6738 *
6739 * <div class="alert alert-info">
6740 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6741 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6742 * then you are also responsible for calling `$destroy` on the transclusion scope.
6743 * </div>
6744 *
6745 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6746 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6747 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6748 *
6749 *
6750 * #### Transclusion Scopes
6751 *
6752 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6753 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6754 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6755 * was taken.
6756 *
6757 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6758 * like this:
6759 *
6760 * ```html
6761 * <div ng-app>
6762 * <div isolate>
6763 * <div transclusion>
6764 * </div>
6765 * </div>
6766 * </div>
6767 * ```
6768 *
6769 * The `$parent` scope hierarchy will look like this:
6770 *
6771 * ```
6772 * - $rootScope
6773 * - isolate
6774 * - transclusion
6775 * ```
6776 *
6777 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6778 *
6779 * ```
6780 * - $rootScope
6781 * - transclusion
6782 * - isolate
6783 * ```
6784 *
6785 *
6786 * ### Attributes
6787 *
6788 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6789 * `link()` or `compile()` functions. It has a variety of uses.
6790 *
6791 * accessing *Normalized attribute names:*
6792 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6793 * the attributes object allows for normalized access to
6794 * the attributes.
6795 *
6796 * * *Directive inter-communication:* All directives share the same instance of the attributes
6797 * object which allows the directives to use the attributes object as inter directive
6798 * communication.
6799 *
6800 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6801 * allowing other directives to read the interpolated value.
6802 *
6803 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6804 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6805 * the only way to easily get the actual value because during the linking phase the interpolation
6806 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6807 *
6808 * ```js
6809 * function linkingFn(scope, elm, attrs, ctrl) {
6810 * // get the attribute value
6811 * console.log(attrs.ngModel);
6812 *
6813 * // change the attribute
6814 * attrs.$set('ngModel', 'new value');
6815 *
6816 * // observe changes to interpolated attribute
6817 * attrs.$observe('ngModel', function(value) {
6818 * console.log('ngModel has changed value to ' + value);
6819 * });
6820 * }
6821 * ```
6822 *
6823 * ## Example
6824 *
6825 * <div class="alert alert-warning">
6826 * **Note**: Typically directives are registered with `module.directive`. The example below is
6827 * to illustrate how `$compile` works.
6828 * </div>
6829 *
6830 <example module="compileExample">
6831 <file name="index.html">
6832 <script>
6833 angular.module('compileExample', [], function($compileProvider) {
6834 // configure new 'compile' directive by passing a directive
6835 // factory function. The factory function injects the '$compile'
6836 $compileProvider.directive('compile', function($compile) {
6837 // directive factory creates a link function
6838 return function(scope, element, attrs) {
6839 scope.$watch(
6840 function(scope) {
6841 // watch the 'compile' expression for changes
6842 return scope.$eval(attrs.compile);
6843 },
6844 function(value) {
6845 // when the 'compile' expression changes
6846 // assign it into the current DOM
6847 element.html(value);
6848
6849 // compile the new DOM and link it to the current
6850 // scope.
6851 // NOTE: we only compile .childNodes so that
6852 // we don't get into infinite loop compiling ourselves
6853 $compile(element.contents())(scope);
6854 }
6855 );
6856 };
6857 });
6858 })
6859 .controller('GreeterController', ['$scope', function($scope) {
6860 $scope.name = 'Angular';
6861 $scope.html = 'Hello {{name}}';
6862 }]);
6863 </script>
6864 <div ng-controller="GreeterController">
6865 <input ng-model="name"> <br/>
6866 <textarea ng-model="html"></textarea> <br/>
6867 <div compile="html"></div>
6868 </div>
6869 </file>
6870 <file name="protractor.js" type="protractor">
6871 it('should auto compile', function() {
6872 var textarea = $('textarea');
6873 var output = $('div[compile]');
6874 // The initial state reads 'Hello Angular'.
6875 expect(output.getText()).toBe('Hello Angular');
6876 textarea.clear();
6877 textarea.sendKeys('{{name}}!');
6878 expect(output.getText()).toBe('Angular!');
6879 });
6880 </file>
6881 </example>
6882
6883 *
6884 *
6885 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
6886 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
6887 *
6888 * <div class="alert alert-danger">
6889 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
6890 * e.g. will not use the right outer scope. Please pass the transclude function as a
6891 * `parentBoundTranscludeFn` to the link function instead.
6892 * </div>
6893 *
6894 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
6895 * root element(s), not their children)
6896 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
6897 * (a DOM element/tree) to a scope. Where:
6898 *
6899 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
6900 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
6901 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
6902 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
6903 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
6904 *
6905 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
6906 * * `scope` - is the current scope with which the linking function is working with.
6907 *
6908 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
6909 * keys may be used to control linking behavior:
6910 *
6911 * * `parentBoundTranscludeFn` - the transclude function made available to
6912 * directives; if given, it will be passed through to the link functions of
6913 * directives found in `element` during compilation.
6914 * * `transcludeControllers` - an object hash with keys that map controller names
6915 * to controller instances; if given, it will make the controllers
6916 * available to directives.
6917 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
6918 * the cloned elements; only needed for transcludes that are allowed to contain non html
6919 * elements (e.g. SVG elements). See also the directive.controller property.
6920 *
6921 * Calling the linking function returns the element of the template. It is either the original
6922 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
6923 *
6924 * After linking the view is not updated until after a call to $digest which typically is done by
6925 * Angular automatically.
6926 *
6927 * If you need access to the bound view, there are two ways to do it:
6928 *
6929 * - If you are not asking the linking function to clone the template, create the DOM element(s)
6930 * before you send them to the compiler and keep this reference around.
6931 * ```js
6932 * var element = $compile('<p>{{total}}</p>')(scope);
6933 * ```
6934 *
6935 * - if on the other hand, you need the element to be cloned, the view reference from the original
6936 * example would not point to the clone, but rather to the original template that was cloned. In
6937 * this case, you can access the clone via the cloneAttachFn:
6938 * ```js
6939 * var templateElement = angular.element('<p>{{total}}</p>'),
6940 * scope = ....;
6941 *
6942 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
6943 * //attach the clone to DOM document at the right place
6944 * });
6945 *
6946 * //now we have reference to the cloned DOM via `clonedElement`
6947 * ```
6948 *
6949 *
6950 * For information on how the compiler works, see the
6951 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
6952 */
6953
6954var $compileMinErr = minErr('$compile');
6955
6956/**
6957 * @ngdoc provider
6958 * @name $compileProvider
6959 *
6960 * @description
6961 */
6962$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
6963function $CompileProvider($provide, $$sanitizeUriProvider) {
6964 var hasDirectives = {},
6965 Suffix = 'Directive',
6966 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
6967 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
6968 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
6969 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
6970
6971 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
6972 // The assumption is that future DOM event attribute names will begin with
6973 // 'on' and be composed of only English letters.
6974 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
6975
6976 function parseIsolateBindings(scope, directiveName, isController) {
6977 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
6978
6979 var bindings = {};
6980
6981 forEach(scope, function(definition, scopeName) {
6982 var match = definition.match(LOCAL_REGEXP);
6983
6984 if (!match) {
6985 throw $compileMinErr('iscp',
6986 "Invalid {3} for directive '{0}'." +
6987 " Definition: {... {1}: '{2}' ...}",
6988 directiveName, scopeName, definition,
6989 (isController ? "controller bindings definition" :
6990 "isolate scope definition"));
6991 }
6992
6993 bindings[scopeName] = {
6994 mode: match[1][0],
6995 collection: match[2] === '*',
6996 optional: match[3] === '?',
6997 attrName: match[4] || scopeName
6998 };
6999 });
7000
7001 return bindings;
7002 }
7003
7004 function parseDirectiveBindings(directive, directiveName) {
7005 var bindings = {
7006 isolateScope: null,
7007 bindToController: null
7008 };
7009 if (isObject(directive.scope)) {
7010 if (directive.bindToController === true) {
7011 bindings.bindToController = parseIsolateBindings(directive.scope,
7012 directiveName, true);
7013 bindings.isolateScope = {};
7014 } else {
7015 bindings.isolateScope = parseIsolateBindings(directive.scope,
7016 directiveName, false);
7017 }
7018 }
7019 if (isObject(directive.bindToController)) {
7020 bindings.bindToController =
7021 parseIsolateBindings(directive.bindToController, directiveName, true);
7022 }
7023 if (isObject(bindings.bindToController)) {
7024 var controller = directive.controller;
7025 var controllerAs = directive.controllerAs;
7026 if (!controller) {
7027 // There is no controller, there may or may not be a controllerAs property
7028 throw $compileMinErr('noctrl',
7029 "Cannot bind to controller without directive '{0}'s controller.",
7030 directiveName);
7031 } else if (!identifierForController(controller, controllerAs)) {
7032 // There is a controller, but no identifier or controllerAs property
7033 throw $compileMinErr('noident',
7034 "Cannot bind to controller without identifier for directive '{0}'.",
7035 directiveName);
7036 }
7037 }
7038 return bindings;
7039 }
7040
7041 function assertValidDirectiveName(name) {
7042 var letter = name.charAt(0);
7043 if (!letter || letter !== lowercase(letter)) {
7044 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7045 }
7046 if (name !== name.trim()) {
7047 throw $compileMinErr('baddir',
7048 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7049 name);
7050 }
7051 }
7052
7053 /**
7054 * @ngdoc method
7055 * @name $compileProvider#directive
7056 * @kind function
7057 *
7058 * @description
7059 * Register a new directive with the compiler.
7060 *
7061 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7062 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7063 * names and the values are the factories.
7064 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7065 * {@link guide/directive} for more info.
7066 * @returns {ng.$compileProvider} Self for chaining.
7067 */
7068 this.directive = function registerDirective(name, directiveFactory) {
7069 assertNotHasOwnProperty(name, 'directive');
7070 if (isString(name)) {
7071 assertValidDirectiveName(name);
7072 assertArg(directiveFactory, 'directiveFactory');
7073 if (!hasDirectives.hasOwnProperty(name)) {
7074 hasDirectives[name] = [];
7075 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7076 function($injector, $exceptionHandler) {
7077 var directives = [];
7078 forEach(hasDirectives[name], function(directiveFactory, index) {
7079 try {
7080 var directive = $injector.invoke(directiveFactory);
7081 if (isFunction(directive)) {
7082 directive = { compile: valueFn(directive) };
7083 } else if (!directive.compile && directive.link) {
7084 directive.compile = valueFn(directive.link);
7085 }
7086 directive.priority = directive.priority || 0;
7087 directive.index = index;
7088 directive.name = directive.name || name;
7089 directive.require = directive.require || (directive.controller && directive.name);
7090 directive.restrict = directive.restrict || 'EA';
7091 var bindings = directive.$$bindings =
7092 parseDirectiveBindings(directive, directive.name);
7093 if (isObject(bindings.isolateScope)) {
7094 directive.$$isolateBindings = bindings.isolateScope;
7095 }
7096 directive.$$moduleName = directiveFactory.$$moduleName;
7097 directives.push(directive);
7098 } catch (e) {
7099 $exceptionHandler(e);
7100 }
7101 });
7102 return directives;
7103 }]);
7104 }
7105 hasDirectives[name].push(directiveFactory);
7106 } else {
7107 forEach(name, reverseParams(registerDirective));
7108 }
7109 return this;
7110 };
7111
7112
7113 /**
7114 * @ngdoc method
7115 * @name $compileProvider#aHrefSanitizationWhitelist
7116 * @kind function
7117 *
7118 * @description
7119 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7120 * urls during a[href] sanitization.
7121 *
7122 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7123 *
7124 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7125 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7126 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7127 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7128 *
7129 * @param {RegExp=} regexp New regexp to whitelist urls with.
7130 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7131 * chaining otherwise.
7132 */
7133 this.aHrefSanitizationWhitelist = function(regexp) {
7134 if (isDefined(regexp)) {
7135 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7136 return this;
7137 } else {
7138 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7139 }
7140 };
7141
7142
7143 /**
7144 * @ngdoc method
7145 * @name $compileProvider#imgSrcSanitizationWhitelist
7146 * @kind function
7147 *
7148 * @description
7149 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7150 * urls during img[src] sanitization.
7151 *
7152 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7153 *
7154 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7155 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7156 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7157 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7158 *
7159 * @param {RegExp=} regexp New regexp to whitelist urls with.
7160 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7161 * chaining otherwise.
7162 */
7163 this.imgSrcSanitizationWhitelist = function(regexp) {
7164 if (isDefined(regexp)) {
7165 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7166 return this;
7167 } else {
7168 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7169 }
7170 };
7171
7172 /**
7173 * @ngdoc method
7174 * @name $compileProvider#debugInfoEnabled
7175 *
7176 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7177 * current debugInfoEnabled state
7178 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7179 *
7180 * @kind function
7181 *
7182 * @description
7183 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7184 * binding information and a reference to the current scope on to DOM elements.
7185 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7186 * * `ng-binding` CSS class
7187 * * `$binding` data property containing an array of the binding expressions
7188 *
7189 * You may want to disable this in production for a significant performance boost. See
7190 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7191 *
7192 * The default value is true.
7193 */
7194 var debugInfoEnabled = true;
7195 this.debugInfoEnabled = function(enabled) {
7196 if (isDefined(enabled)) {
7197 debugInfoEnabled = enabled;
7198 return this;
7199 }
7200 return debugInfoEnabled;
7201 };
7202
7203 this.$get = [
7204 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7205 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7206 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7207 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7208
7209 var Attributes = function(element, attributesToCopy) {
7210 if (attributesToCopy) {
7211 var keys = Object.keys(attributesToCopy);
7212 var i, l, key;
7213
7214 for (i = 0, l = keys.length; i < l; i++) {
7215 key = keys[i];
7216 this[key] = attributesToCopy[key];
7217 }
7218 } else {
7219 this.$attr = {};
7220 }
7221
7222 this.$$element = element;
7223 };
7224
7225 Attributes.prototype = {
7226 /**
7227 * @ngdoc method
7228 * @name $compile.directive.Attributes#$normalize
7229 * @kind function
7230 *
7231 * @description
7232 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7233 * `data-`) to its normalized, camelCase form.
7234 *
7235 * Also there is special case for Moz prefix starting with upper case letter.
7236 *
7237 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7238 *
7239 * @param {string} name Name to normalize
7240 */
7241 $normalize: directiveNormalize,
7242
7243
7244 /**
7245 * @ngdoc method
7246 * @name $compile.directive.Attributes#$addClass
7247 * @kind function
7248 *
7249 * @description
7250 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7251 * are enabled then an animation will be triggered for the class addition.
7252 *
7253 * @param {string} classVal The className value that will be added to the element
7254 */
7255 $addClass: function(classVal) {
7256 if (classVal && classVal.length > 0) {
7257 $animate.addClass(this.$$element, classVal);
7258 }
7259 },
7260
7261 /**
7262 * @ngdoc method
7263 * @name $compile.directive.Attributes#$removeClass
7264 * @kind function
7265 *
7266 * @description
7267 * Removes the CSS class value specified by the classVal parameter from the element. If
7268 * animations are enabled then an animation will be triggered for the class removal.
7269 *
7270 * @param {string} classVal The className value that will be removed from the element
7271 */
7272 $removeClass: function(classVal) {
7273 if (classVal && classVal.length > 0) {
7274 $animate.removeClass(this.$$element, classVal);
7275 }
7276 },
7277
7278 /**
7279 * @ngdoc method
7280 * @name $compile.directive.Attributes#$updateClass
7281 * @kind function
7282 *
7283 * @description
7284 * Adds and removes the appropriate CSS class values to the element based on the difference
7285 * between the new and old CSS class values (specified as newClasses and oldClasses).
7286 *
7287 * @param {string} newClasses The current CSS className value
7288 * @param {string} oldClasses The former CSS className value
7289 */
7290 $updateClass: function(newClasses, oldClasses) {
7291 var toAdd = tokenDifference(newClasses, oldClasses);
7292 if (toAdd && toAdd.length) {
7293 $animate.addClass(this.$$element, toAdd);
7294 }
7295
7296 var toRemove = tokenDifference(oldClasses, newClasses);
7297 if (toRemove && toRemove.length) {
7298 $animate.removeClass(this.$$element, toRemove);
7299 }
7300 },
7301
7302 /**
7303 * Set a normalized attribute on the element in a way such that all directives
7304 * can share the attribute. This function properly handles boolean attributes.
7305 * @param {string} key Normalized key. (ie ngAttribute)
7306 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7307 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7308 * Defaults to true.
7309 * @param {string=} attrName Optional none normalized name. Defaults to key.
7310 */
7311 $set: function(key, value, writeAttr, attrName) {
7312 // TODO: decide whether or not to throw an error if "class"
7313 //is set through this function since it may cause $updateClass to
7314 //become unstable.
7315
7316 var node = this.$$element[0],
7317 booleanKey = getBooleanAttrName(node, key),
7318 aliasedKey = getAliasedAttrName(key),
7319 observer = key,
7320 nodeName;
7321
7322 if (booleanKey) {
7323 this.$$element.prop(key, value);
7324 attrName = booleanKey;
7325 } else if (aliasedKey) {
7326 this[aliasedKey] = value;
7327 observer = aliasedKey;
7328 }
7329
7330 this[key] = value;
7331
7332 // translate normalized key to actual key
7333 if (attrName) {
7334 this.$attr[key] = attrName;
7335 } else {
7336 attrName = this.$attr[key];
7337 if (!attrName) {
7338 this.$attr[key] = attrName = snake_case(key, '-');
7339 }
7340 }
7341
7342 nodeName = nodeName_(this.$$element);
7343
7344 if ((nodeName === 'a' && key === 'href') ||
7345 (nodeName === 'img' && key === 'src')) {
7346 // sanitize a[href] and img[src] values
7347 this[key] = value = $$sanitizeUri(value, key === 'src');
7348 } else if (nodeName === 'img' && key === 'srcset') {
7349 // sanitize img[srcset] values
7350 var result = "";
7351
7352 // first check if there are spaces because it's not the same pattern
7353 var trimmedSrcset = trim(value);
7354 // ( 999x ,| 999w ,| ,|, )
7355 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7356 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7357
7358 // split srcset into tuple of uri and descriptor except for the last item
7359 var rawUris = trimmedSrcset.split(pattern);
7360
7361 // for each tuples
7362 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7363 for (var i = 0; i < nbrUrisWith2parts; i++) {
7364 var innerIdx = i * 2;
7365 // sanitize the uri
7366 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7367 // add the descriptor
7368 result += (" " + trim(rawUris[innerIdx + 1]));
7369 }
7370
7371 // split the last item into uri and descriptor
7372 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7373
7374 // sanitize the last uri
7375 result += $$sanitizeUri(trim(lastTuple[0]), true);
7376
7377 // and add the last descriptor if any
7378 if (lastTuple.length === 2) {
7379 result += (" " + trim(lastTuple[1]));
7380 }
7381 this[key] = value = result;
7382 }
7383
7384 if (writeAttr !== false) {
7385 if (value === null || isUndefined(value)) {
7386 this.$$element.removeAttr(attrName);
7387 } else {
7388 this.$$element.attr(attrName, value);
7389 }
7390 }
7391
7392 // fire observers
7393 var $$observers = this.$$observers;
7394 $$observers && forEach($$observers[observer], function(fn) {
7395 try {
7396 fn(value);
7397 } catch (e) {
7398 $exceptionHandler(e);
7399 }
7400 });
7401 },
7402
7403
7404 /**
7405 * @ngdoc method
7406 * @name $compile.directive.Attributes#$observe
7407 * @kind function
7408 *
7409 * @description
7410 * Observes an interpolated attribute.
7411 *
7412 * The observer function will be invoked once during the next `$digest` following
7413 * compilation. The observer is then invoked whenever the interpolated value
7414 * changes.
7415 *
7416 * @param {string} key Normalized key. (ie ngAttribute) .
7417 * @param {function(interpolatedValue)} fn Function that will be called whenever
7418 the interpolated value of the attribute changes.
7419 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7420 * @returns {function()} Returns a deregistration function for this observer.
7421 */
7422 $observe: function(key, fn) {
7423 var attrs = this,
7424 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7425 listeners = ($$observers[key] || ($$observers[key] = []));
7426
7427 listeners.push(fn);
7428 $rootScope.$evalAsync(function() {
7429 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7430 // no one registered attribute interpolation function, so lets call it manually
7431 fn(attrs[key]);
7432 }
7433 });
7434
7435 return function() {
7436 arrayRemove(listeners, fn);
7437 };
7438 }
7439 };
7440
7441
7442 function safeAddClass($element, className) {
7443 try {
7444 $element.addClass(className);
7445 } catch (e) {
7446 // ignore, since it means that we are trying to set class on
7447 // SVG element, where class name is read-only.
7448 }
7449 }
7450
7451
7452 var startSymbol = $interpolate.startSymbol(),
7453 endSymbol = $interpolate.endSymbol(),
7454 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7455 ? identity
7456 : function denormalizeTemplate(template) {
7457 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7458 },
7459 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7460
7461 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7462 var bindings = $element.data('$binding') || [];
7463
7464 if (isArray(binding)) {
7465 bindings = bindings.concat(binding);
7466 } else {
7467 bindings.push(binding);
7468 }
7469
7470 $element.data('$binding', bindings);
7471 } : noop;
7472
7473 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7474 safeAddClass($element, 'ng-binding');
7475 } : noop;
7476
7477 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7478 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7479 $element.data(dataName, scope);
7480 } : noop;
7481
7482 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7483 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7484 } : noop;
7485
7486 return compile;
7487
7488 //================================
7489
7490 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7491 previousCompileContext) {
7492 if (!($compileNodes instanceof jqLite)) {
7493 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7494 // modify it.
7495 $compileNodes = jqLite($compileNodes);
7496 }
7497 // We can not compile top level text elements since text nodes can be merged and we will
7498 // not be able to attach scope data to them, so we will wrap them in <span>
7499 forEach($compileNodes, function(node, index) {
7500 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7501 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7502 }
7503 });
7504 var compositeLinkFn =
7505 compileNodes($compileNodes, transcludeFn, $compileNodes,
7506 maxPriority, ignoreDirective, previousCompileContext);
7507 compile.$$addScopeClass($compileNodes);
7508 var namespace = null;
7509 return function publicLinkFn(scope, cloneConnectFn, options) {
7510 assertArg(scope, 'scope');
7511
7512 options = options || {};
7513 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7514 transcludeControllers = options.transcludeControllers,
7515 futureParentElement = options.futureParentElement;
7516
7517 // When `parentBoundTranscludeFn` is passed, it is a
7518 // `controllersBoundTransclude` function (it was previously passed
7519 // as `transclude` to directive.link) so we must unwrap it to get
7520 // its `boundTranscludeFn`
7521 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7522 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7523 }
7524
7525 if (!namespace) {
7526 namespace = detectNamespaceForChildElements(futureParentElement);
7527 }
7528 var $linkNode;
7529 if (namespace !== 'html') {
7530 // When using a directive with replace:true and templateUrl the $compileNodes
7531 // (or a child element inside of them)
7532 // might change, so we need to recreate the namespace adapted compileNodes
7533 // for call to the link function.
7534 // Note: This will already clone the nodes...
7535 $linkNode = jqLite(
7536 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7537 );
7538 } else if (cloneConnectFn) {
7539 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7540 // and sometimes changes the structure of the DOM.
7541 $linkNode = JQLitePrototype.clone.call($compileNodes);
7542 } else {
7543 $linkNode = $compileNodes;
7544 }
7545
7546 if (transcludeControllers) {
7547 for (var controllerName in transcludeControllers) {
7548 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7549 }
7550 }
7551
7552 compile.$$addScopeInfo($linkNode, scope);
7553
7554 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7555 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7556 return $linkNode;
7557 };
7558 }
7559
7560 function detectNamespaceForChildElements(parentElement) {
7561 // TODO: Make this detect MathML as well...
7562 var node = parentElement && parentElement[0];
7563 if (!node) {
7564 return 'html';
7565 } else {
7566 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7567 }
7568 }
7569
7570 /**
7571 * Compile function matches each node in nodeList against the directives. Once all directives
7572 * for a particular node are collected their compile functions are executed. The compile
7573 * functions return values - the linking functions - are combined into a composite linking
7574 * function, which is the a linking function for the node.
7575 *
7576 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7577 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7578 * scope argument is auto-generated to the new child of the transcluded parent scope.
7579 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7580 * the rootElement must be set the jqLite collection of the compile root. This is
7581 * needed so that the jqLite collection items can be replaced with widgets.
7582 * @param {number=} maxPriority Max directive priority.
7583 * @returns {Function} A composite linking function of all of the matched directives or null.
7584 */
7585 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7586 previousCompileContext) {
7587 var linkFns = [],
7588 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7589
7590 for (var i = 0; i < nodeList.length; i++) {
7591 attrs = new Attributes();
7592
7593 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7594 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7595 ignoreDirective);
7596
7597 nodeLinkFn = (directives.length)
7598 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7599 null, [], [], previousCompileContext)
7600 : null;
7601
7602 if (nodeLinkFn && nodeLinkFn.scope) {
7603 compile.$$addScopeClass(attrs.$$element);
7604 }
7605
7606 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7607 !(childNodes = nodeList[i].childNodes) ||
7608 !childNodes.length)
7609 ? null
7610 : compileNodes(childNodes,
7611 nodeLinkFn ? (
7612 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7613 && nodeLinkFn.transclude) : transcludeFn);
7614
7615 if (nodeLinkFn || childLinkFn) {
7616 linkFns.push(i, nodeLinkFn, childLinkFn);
7617 linkFnFound = true;
7618 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7619 }
7620
7621 //use the previous context only for the first element in the virtual group
7622 previousCompileContext = null;
7623 }
7624
7625 // return a linking function if we have found anything, null otherwise
7626 return linkFnFound ? compositeLinkFn : null;
7627
7628 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7629 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7630 var stableNodeList;
7631
7632
7633 if (nodeLinkFnFound) {
7634 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7635 // offsets don't get screwed up
7636 var nodeListLength = nodeList.length;
7637 stableNodeList = new Array(nodeListLength);
7638
7639 // create a sparse array by only copying the elements which have a linkFn
7640 for (i = 0; i < linkFns.length; i+=3) {
7641 idx = linkFns[i];
7642 stableNodeList[idx] = nodeList[idx];
7643 }
7644 } else {
7645 stableNodeList = nodeList;
7646 }
7647
7648 for (i = 0, ii = linkFns.length; i < ii;) {
7649 node = stableNodeList[linkFns[i++]];
7650 nodeLinkFn = linkFns[i++];
7651 childLinkFn = linkFns[i++];
7652
7653 if (nodeLinkFn) {
7654 if (nodeLinkFn.scope) {
7655 childScope = scope.$new();
7656 compile.$$addScopeInfo(jqLite(node), childScope);
7657 var destroyBindings = nodeLinkFn.$$destroyBindings;
7658 if (destroyBindings) {
7659 nodeLinkFn.$$destroyBindings = null;
7660 childScope.$on('$destroyed', destroyBindings);
7661 }
7662 } else {
7663 childScope = scope;
7664 }
7665
7666 if (nodeLinkFn.transcludeOnThisElement) {
7667 childBoundTranscludeFn = createBoundTranscludeFn(
7668 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7669
7670 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7671 childBoundTranscludeFn = parentBoundTranscludeFn;
7672
7673 } else if (!parentBoundTranscludeFn && transcludeFn) {
7674 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7675
7676 } else {
7677 childBoundTranscludeFn = null;
7678 }
7679
7680 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
7681 nodeLinkFn);
7682
7683 } else if (childLinkFn) {
7684 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7685 }
7686 }
7687 }
7688 }
7689
7690 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7691
7692 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7693
7694 if (!transcludedScope) {
7695 transcludedScope = scope.$new(false, containingScope);
7696 transcludedScope.$$transcluded = true;
7697 }
7698
7699 return transcludeFn(transcludedScope, cloneFn, {
7700 parentBoundTranscludeFn: previousBoundTranscludeFn,
7701 transcludeControllers: controllers,
7702 futureParentElement: futureParentElement
7703 });
7704 };
7705
7706 return boundTranscludeFn;
7707 }
7708
7709 /**
7710 * Looks for directives on the given node and adds them to the directive collection which is
7711 * sorted.
7712 *
7713 * @param node Node to search.
7714 * @param directives An array to which the directives are added to. This array is sorted before
7715 * the function returns.
7716 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7717 * @param {number=} maxPriority Max directive priority.
7718 */
7719 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7720 var nodeType = node.nodeType,
7721 attrsMap = attrs.$attr,
7722 match,
7723 className;
7724
7725 switch (nodeType) {
7726 case NODE_TYPE_ELEMENT: /* Element */
7727 // use the node name: <directive>
7728 addDirective(directives,
7729 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7730
7731 // iterate over the attributes
7732 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7733 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7734 var attrStartName = false;
7735 var attrEndName = false;
7736
7737 attr = nAttrs[j];
7738 name = attr.name;
7739 value = trim(attr.value);
7740
7741 // support ngAttr attribute binding
7742 ngAttrName = directiveNormalize(name);
7743 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7744 name = name.replace(PREFIX_REGEXP, '')
7745 .substr(8).replace(/_(.)/g, function(match, letter) {
7746 return letter.toUpperCase();
7747 });
7748 }
7749
7750 var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
7751 if (directiveIsMultiElement(directiveNName)) {
7752 if (ngAttrName === directiveNName + 'Start') {
7753 attrStartName = name;
7754 attrEndName = name.substr(0, name.length - 5) + 'end';
7755 name = name.substr(0, name.length - 6);
7756 }
7757 }
7758
7759 nName = directiveNormalize(name.toLowerCase());
7760 attrsMap[nName] = name;
7761 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7762 attrs[nName] = value;
7763 if (getBooleanAttrName(node, nName)) {
7764 attrs[nName] = true; // presence means true
7765 }
7766 }
7767 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7768 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7769 attrEndName);
7770 }
7771
7772 // use class as directive
7773 className = node.className;
7774 if (isObject(className)) {
7775 // Maybe SVGAnimatedString
7776 className = className.animVal;
7777 }
7778 if (isString(className) && className !== '') {
7779 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7780 nName = directiveNormalize(match[2]);
7781 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7782 attrs[nName] = trim(match[3]);
7783 }
7784 className = className.substr(match.index + match[0].length);
7785 }
7786 }
7787 break;
7788 case NODE_TYPE_TEXT: /* Text Node */
7789 if (msie === 11) {
7790 // Workaround for #11781
7791 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7792 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7793 node.parentNode.removeChild(node.nextSibling);
7794 }
7795 }
7796 addTextInterpolateDirective(directives, node.nodeValue);
7797 break;
7798 case NODE_TYPE_COMMENT: /* Comment */
7799 try {
7800 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7801 if (match) {
7802 nName = directiveNormalize(match[1]);
7803 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7804 attrs[nName] = trim(match[2]);
7805 }
7806 }
7807 } catch (e) {
7808 // turns out that under some circumstances IE9 throws errors when one attempts to read
7809 // comment's node value.
7810 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7811 }
7812 break;
7813 }
7814
7815 directives.sort(byPriority);
7816 return directives;
7817 }
7818
7819 /**
7820 * Given a node with an directive-start it collects all of the siblings until it finds
7821 * directive-end.
7822 * @param node
7823 * @param attrStart
7824 * @param attrEnd
7825 * @returns {*}
7826 */
7827 function groupScan(node, attrStart, attrEnd) {
7828 var nodes = [];
7829 var depth = 0;
7830 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7831 do {
7832 if (!node) {
7833 throw $compileMinErr('uterdir',
7834 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7835 attrStart, attrEnd);
7836 }
7837 if (node.nodeType == NODE_TYPE_ELEMENT) {
7838 if (node.hasAttribute(attrStart)) depth++;
7839 if (node.hasAttribute(attrEnd)) depth--;
7840 }
7841 nodes.push(node);
7842 node = node.nextSibling;
7843 } while (depth > 0);
7844 } else {
7845 nodes.push(node);
7846 }
7847
7848 return jqLite(nodes);
7849 }
7850
7851 /**
7852 * Wrapper for linking function which converts normal linking function into a grouped
7853 * linking function.
7854 * @param linkFn
7855 * @param attrStart
7856 * @param attrEnd
7857 * @returns {Function}
7858 */
7859 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7860 return function(scope, element, attrs, controllers, transcludeFn) {
7861 element = groupScan(element[0], attrStart, attrEnd);
7862 return linkFn(scope, element, attrs, controllers, transcludeFn);
7863 };
7864 }
7865
7866 /**
7867 * Once the directives have been collected, their compile functions are executed. This method
7868 * is responsible for inlining directive templates as well as terminating the application
7869 * of the directives if the terminal directive has been reached.
7870 *
7871 * @param {Array} directives Array of collected directives to execute their compile function.
7872 * this needs to be pre-sorted by priority order.
7873 * @param {Node} compileNode The raw DOM node to apply the compile functions to
7874 * @param {Object} templateAttrs The shared attribute function
7875 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7876 * scope argument is auto-generated to the new
7877 * child of the transcluded parent scope.
7878 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
7879 * argument has the root jqLite array so that we can replace nodes
7880 * on it.
7881 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
7882 * compiling the transclusion.
7883 * @param {Array.<Function>} preLinkFns
7884 * @param {Array.<Function>} postLinkFns
7885 * @param {Object} previousCompileContext Context used for previous compilation of the current
7886 * node
7887 * @returns {Function} linkFn
7888 */
7889 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
7890 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
7891 previousCompileContext) {
7892 previousCompileContext = previousCompileContext || {};
7893
7894 var terminalPriority = -Number.MAX_VALUE,
7895 newScopeDirective = previousCompileContext.newScopeDirective,
7896 controllerDirectives = previousCompileContext.controllerDirectives,
7897 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
7898 templateDirective = previousCompileContext.templateDirective,
7899 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
7900 hasTranscludeDirective = false,
7901 hasTemplate = false,
7902 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
7903 $compileNode = templateAttrs.$$element = jqLite(compileNode),
7904 directive,
7905 directiveName,
7906 $template,
7907 replaceDirective = originalReplaceDirective,
7908 childTranscludeFn = transcludeFn,
7909 linkFn,
7910 directiveValue;
7911
7912 // executes all directives on the current element
7913 for (var i = 0, ii = directives.length; i < ii; i++) {
7914 directive = directives[i];
7915 var attrStart = directive.$$start;
7916 var attrEnd = directive.$$end;
7917
7918 // collect multiblock sections
7919 if (attrStart) {
7920 $compileNode = groupScan(compileNode, attrStart, attrEnd);
7921 }
7922 $template = undefined;
7923
7924 if (terminalPriority > directive.priority) {
7925 break; // prevent further processing of directives
7926 }
7927
7928 if (directiveValue = directive.scope) {
7929
7930 // skip the check for directives with async templates, we'll check the derived sync
7931 // directive when the template arrives
7932 if (!directive.templateUrl) {
7933 if (isObject(directiveValue)) {
7934 // This directive is trying to add an isolated scope.
7935 // Check that there is no scope of any kind already
7936 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
7937 directive, $compileNode);
7938 newIsolateScopeDirective = directive;
7939 } else {
7940 // This directive is trying to add a child scope.
7941 // Check that there is no isolated scope already
7942 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
7943 $compileNode);
7944 }
7945 }
7946
7947 newScopeDirective = newScopeDirective || directive;
7948 }
7949
7950 directiveName = directive.name;
7951
7952 if (!directive.templateUrl && directive.controller) {
7953 directiveValue = directive.controller;
7954 controllerDirectives = controllerDirectives || createMap();
7955 assertNoDuplicate("'" + directiveName + "' controller",
7956 controllerDirectives[directiveName], directive, $compileNode);
7957 controllerDirectives[directiveName] = directive;
7958 }
7959
7960 if (directiveValue = directive.transclude) {
7961 hasTranscludeDirective = true;
7962
7963 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
7964 // This option should only be used by directives that know how to safely handle element transclusion,
7965 // where the transcluded nodes are added or replaced after linking.
7966 if (!directive.$$tlb) {
7967 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
7968 nonTlbTranscludeDirective = directive;
7969 }
7970
7971 if (directiveValue == 'element') {
7972 hasElementTranscludeDirective = true;
7973 terminalPriority = directive.priority;
7974 $template = $compileNode;
7975 $compileNode = templateAttrs.$$element =
7976 jqLite(document.createComment(' ' + directiveName + ': ' +
7977 templateAttrs[directiveName] + ' '));
7978 compileNode = $compileNode[0];
7979 replaceWith(jqCollection, sliceArgs($template), compileNode);
7980
7981 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
7982 replaceDirective && replaceDirective.name, {
7983 // Don't pass in:
7984 // - controllerDirectives - otherwise we'll create duplicates controllers
7985 // - newIsolateScopeDirective or templateDirective - combining templates with
7986 // element transclusion doesn't make sense.
7987 //
7988 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
7989 // on the same element more than once.
7990 nonTlbTranscludeDirective: nonTlbTranscludeDirective
7991 });
7992 } else {
7993 $template = jqLite(jqLiteClone(compileNode)).contents();
7994 $compileNode.empty(); // clear contents
7995 childTranscludeFn = compile($template, transcludeFn);
7996 }
7997 }
7998
7999 if (directive.template) {
8000 hasTemplate = true;
8001 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8002 templateDirective = directive;
8003
8004 directiveValue = (isFunction(directive.template))
8005 ? directive.template($compileNode, templateAttrs)
8006 : directive.template;
8007
8008 directiveValue = denormalizeTemplate(directiveValue);
8009
8010 if (directive.replace) {
8011 replaceDirective = directive;
8012 if (jqLiteIsTextNode(directiveValue)) {
8013 $template = [];
8014 } else {
8015 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8016 }
8017 compileNode = $template[0];
8018
8019 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8020 throw $compileMinErr('tplrt',
8021 "Template for directive '{0}' must have exactly one root element. {1}",
8022 directiveName, '');
8023 }
8024
8025 replaceWith(jqCollection, $compileNode, compileNode);
8026
8027 var newTemplateAttrs = {$attr: {}};
8028
8029 // combine directives from the original node and from the template:
8030 // - take the array of directives for this element
8031 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8032 // - collect directives from the template and sort them by priority
8033 // - combine directives as: processed + template + unprocessed
8034 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8035 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8036
8037 if (newIsolateScopeDirective) {
8038 markDirectivesAsIsolate(templateDirectives);
8039 }
8040 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8041 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8042
8043 ii = directives.length;
8044 } else {
8045 $compileNode.html(directiveValue);
8046 }
8047 }
8048
8049 if (directive.templateUrl) {
8050 hasTemplate = true;
8051 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8052 templateDirective = directive;
8053
8054 if (directive.replace) {
8055 replaceDirective = directive;
8056 }
8057
8058 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8059 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8060 controllerDirectives: controllerDirectives,
8061 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8062 newIsolateScopeDirective: newIsolateScopeDirective,
8063 templateDirective: templateDirective,
8064 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8065 });
8066 ii = directives.length;
8067 } else if (directive.compile) {
8068 try {
8069 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8070 if (isFunction(linkFn)) {
8071 addLinkFns(null, linkFn, attrStart, attrEnd);
8072 } else if (linkFn) {
8073 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8074 }
8075 } catch (e) {
8076 $exceptionHandler(e, startingTag($compileNode));
8077 }
8078 }
8079
8080 if (directive.terminal) {
8081 nodeLinkFn.terminal = true;
8082 terminalPriority = Math.max(terminalPriority, directive.priority);
8083 }
8084
8085 }
8086
8087 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8088 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8089 nodeLinkFn.templateOnThisElement = hasTemplate;
8090 nodeLinkFn.transclude = childTranscludeFn;
8091
8092 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8093
8094 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8095 return nodeLinkFn;
8096
8097 ////////////////////
8098
8099 function addLinkFns(pre, post, attrStart, attrEnd) {
8100 if (pre) {
8101 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8102 pre.require = directive.require;
8103 pre.directiveName = directiveName;
8104 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8105 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8106 }
8107 preLinkFns.push(pre);
8108 }
8109 if (post) {
8110 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8111 post.require = directive.require;
8112 post.directiveName = directiveName;
8113 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8114 post = cloneAndAnnotateFn(post, {isolateScope: true});
8115 }
8116 postLinkFns.push(post);
8117 }
8118 }
8119
8120
8121 function getControllers(directiveName, require, $element, elementControllers) {
8122 var value;
8123
8124 if (isString(require)) {
8125 var match = require.match(REQUIRE_PREFIX_REGEXP);
8126 var name = require.substring(match[0].length);
8127 var inheritType = match[1] || match[3];
8128 var optional = match[2] === '?';
8129
8130 //If only parents then start at the parent element
8131 if (inheritType === '^^') {
8132 $element = $element.parent();
8133 //Otherwise attempt getting the controller from elementControllers in case
8134 //the element is transcluded (and has no data) and to avoid .data if possible
8135 } else {
8136 value = elementControllers && elementControllers[name];
8137 value = value && value.instance;
8138 }
8139
8140 if (!value) {
8141 var dataName = '$' + name + 'Controller';
8142 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8143 }
8144
8145 if (!value && !optional) {
8146 throw $compileMinErr('ctreq',
8147 "Controller '{0}', required by directive '{1}', can't be found!",
8148 name, directiveName);
8149 }
8150 } else if (isArray(require)) {
8151 value = [];
8152 for (var i = 0, ii = require.length; i < ii; i++) {
8153 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8154 }
8155 }
8156
8157 return value || null;
8158 }
8159
8160 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8161 var elementControllers = createMap();
8162 for (var controllerKey in controllerDirectives) {
8163 var directive = controllerDirectives[controllerKey];
8164 var locals = {
8165 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8166 $element: $element,
8167 $attrs: attrs,
8168 $transclude: transcludeFn
8169 };
8170
8171 var controller = directive.controller;
8172 if (controller == '@') {
8173 controller = attrs[directive.name];
8174 }
8175
8176 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8177
8178 // For directives with element transclusion the element is a comment,
8179 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8180 // clean up (http://bugs.jquery.com/ticket/8335).
8181 // Instead, we save the controllers for the element in a local hash and attach to .data
8182 // later, once we have the actual element.
8183 elementControllers[directive.name] = controllerInstance;
8184 if (!hasElementTranscludeDirective) {
8185 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8186 }
8187 }
8188 return elementControllers;
8189 }
8190
8191 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
8192 thisLinkFn) {
8193 var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
8194 attrs;
8195
8196 if (compileNode === linkNode) {
8197 attrs = templateAttrs;
8198 $element = templateAttrs.$$element;
8199 } else {
8200 $element = jqLite(linkNode);
8201 attrs = new Attributes($element, templateAttrs);
8202 }
8203
8204 if (newIsolateScopeDirective) {
8205 isolateScope = scope.$new(true);
8206 }
8207
8208 if (boundTranscludeFn) {
8209 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8210 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8211 transcludeFn = controllersBoundTransclude;
8212 transcludeFn.$$boundTransclude = boundTranscludeFn;
8213 }
8214
8215 if (controllerDirectives) {
8216 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8217 }
8218
8219 if (newIsolateScopeDirective) {
8220 // Initialize isolate scope bindings for new isolate scope directive.
8221 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8222 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8223 compile.$$addScopeClass($element, true);
8224 isolateScope.$$isolateBindings =
8225 newIsolateScopeDirective.$$isolateBindings;
8226 initializeDirectiveBindings(scope, attrs, isolateScope,
8227 isolateScope.$$isolateBindings,
8228 newIsolateScopeDirective, isolateScope);
8229 }
8230 if (elementControllers) {
8231 // Initialize bindToController bindings for new/isolate scopes
8232 var scopeDirective = newIsolateScopeDirective || newScopeDirective;
8233 var bindings;
8234 var controllerForBindings;
8235 if (scopeDirective && elementControllers[scopeDirective.name]) {
8236 bindings = scopeDirective.$$bindings.bindToController;
8237 controller = elementControllers[scopeDirective.name];
8238
8239 if (controller && controller.identifier && bindings) {
8240 controllerForBindings = controller;
8241 thisLinkFn.$$destroyBindings =
8242 initializeDirectiveBindings(scope, attrs, controller.instance,
8243 bindings, scopeDirective);
8244 }
8245 }
8246 for (i in elementControllers) {
8247 controller = elementControllers[i];
8248 var controllerResult = controller();
8249
8250 if (controllerResult !== controller.instance) {
8251 // If the controller constructor has a return value, overwrite the instance
8252 // from setupControllers and update the element data
8253 controller.instance = controllerResult;
8254 $element.data('$' + i + 'Controller', controllerResult);
8255 if (controller === controllerForBindings) {
8256 // Remove and re-install bindToController bindings
8257 thisLinkFn.$$destroyBindings();
8258 thisLinkFn.$$destroyBindings =
8259 initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
8260 }
8261 }
8262 }
8263 }
8264
8265 // PRELINKING
8266 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8267 linkFn = preLinkFns[i];
8268 invokeLinkFn(linkFn,
8269 linkFn.isolateScope ? isolateScope : scope,
8270 $element,
8271 attrs,
8272 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8273 transcludeFn
8274 );
8275 }
8276
8277 // RECURSION
8278 // We only pass the isolate scope, if the isolate directive has a template,
8279 // otherwise the child elements do not belong to the isolate directive.
8280 var scopeToChild = scope;
8281 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8282 scopeToChild = isolateScope;
8283 }
8284 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8285
8286 // POSTLINKING
8287 for (i = postLinkFns.length - 1; i >= 0; i--) {
8288 linkFn = postLinkFns[i];
8289 invokeLinkFn(linkFn,
8290 linkFn.isolateScope ? isolateScope : scope,
8291 $element,
8292 attrs,
8293 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8294 transcludeFn
8295 );
8296 }
8297
8298 // This is the function that is injected as `$transclude`.
8299 // Note: all arguments are optional!
8300 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8301 var transcludeControllers;
8302
8303 // No scope passed in:
8304 if (!isScope(scope)) {
8305 futureParentElement = cloneAttachFn;
8306 cloneAttachFn = scope;
8307 scope = undefined;
8308 }
8309
8310 if (hasElementTranscludeDirective) {
8311 transcludeControllers = elementControllers;
8312 }
8313 if (!futureParentElement) {
8314 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8315 }
8316 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8317 }
8318 }
8319 }
8320
8321 function markDirectivesAsIsolate(directives) {
8322 // mark all directives as needing isolate scope.
8323 for (var j = 0, jj = directives.length; j < jj; j++) {
8324 directives[j] = inherit(directives[j], {$$isolateScope: true});
8325 }
8326 }
8327
8328 /**
8329 * looks up the directive and decorates it with exception handling and proper parameters. We
8330 * call this the boundDirective.
8331 *
8332 * @param {string} name name of the directive to look up.
8333 * @param {string} location The directive must be found in specific format.
8334 * String containing any of theses characters:
8335 *
8336 * * `E`: element name
8337 * * `A': attribute
8338 * * `C`: class
8339 * * `M`: comment
8340 * @returns {boolean} true if directive was added.
8341 */
8342 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8343 endAttrName) {
8344 if (name === ignoreDirective) return null;
8345 var match = null;
8346 if (hasDirectives.hasOwnProperty(name)) {
8347 for (var directive, directives = $injector.get(name + Suffix),
8348 i = 0, ii = directives.length; i < ii; i++) {
8349 try {
8350 directive = directives[i];
8351 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8352 directive.restrict.indexOf(location) != -1) {
8353 if (startAttrName) {
8354 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8355 }
8356 tDirectives.push(directive);
8357 match = directive;
8358 }
8359 } catch (e) { $exceptionHandler(e); }
8360 }
8361 }
8362 return match;
8363 }
8364
8365
8366 /**
8367 * looks up the directive and returns true if it is a multi-element directive,
8368 * and therefore requires DOM nodes between -start and -end markers to be grouped
8369 * together.
8370 *
8371 * @param {string} name name of the directive to look up.
8372 * @returns true if directive was registered as multi-element.
8373 */
8374 function directiveIsMultiElement(name) {
8375 if (hasDirectives.hasOwnProperty(name)) {
8376 for (var directive, directives = $injector.get(name + Suffix),
8377 i = 0, ii = directives.length; i < ii; i++) {
8378 directive = directives[i];
8379 if (directive.multiElement) {
8380 return true;
8381 }
8382 }
8383 }
8384 return false;
8385 }
8386
8387 /**
8388 * When the element is replaced with HTML template then the new attributes
8389 * on the template need to be merged with the existing attributes in the DOM.
8390 * The desired effect is to have both of the attributes present.
8391 *
8392 * @param {object} dst destination attributes (original DOM)
8393 * @param {object} src source attributes (from the directive template)
8394 */
8395 function mergeTemplateAttributes(dst, src) {
8396 var srcAttr = src.$attr,
8397 dstAttr = dst.$attr,
8398 $element = dst.$$element;
8399
8400 // reapply the old attributes to the new element
8401 forEach(dst, function(value, key) {
8402 if (key.charAt(0) != '$') {
8403 if (src[key] && src[key] !== value) {
8404 value += (key === 'style' ? ';' : ' ') + src[key];
8405 }
8406 dst.$set(key, value, true, srcAttr[key]);
8407 }
8408 });
8409
8410 // copy the new attributes on the old attrs object
8411 forEach(src, function(value, key) {
8412 if (key == 'class') {
8413 safeAddClass($element, value);
8414 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8415 } else if (key == 'style') {
8416 $element.attr('style', $element.attr('style') + ';' + value);
8417 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8418 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8419 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8420 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8421 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8422 dst[key] = value;
8423 dstAttr[key] = srcAttr[key];
8424 }
8425 });
8426 }
8427
8428
8429 function compileTemplateUrl(directives, $compileNode, tAttrs,
8430 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8431 var linkQueue = [],
8432 afterTemplateNodeLinkFn,
8433 afterTemplateChildLinkFn,
8434 beforeTemplateCompileNode = $compileNode[0],
8435 origAsyncDirective = directives.shift(),
8436 derivedSyncDirective = inherit(origAsyncDirective, {
8437 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8438 }),
8439 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8440 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8441 : origAsyncDirective.templateUrl,
8442 templateNamespace = origAsyncDirective.templateNamespace;
8443
8444 $compileNode.empty();
8445
8446 $templateRequest(templateUrl)
8447 .then(function(content) {
8448 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8449
8450 content = denormalizeTemplate(content);
8451
8452 if (origAsyncDirective.replace) {
8453 if (jqLiteIsTextNode(content)) {
8454 $template = [];
8455 } else {
8456 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8457 }
8458 compileNode = $template[0];
8459
8460 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8461 throw $compileMinErr('tplrt',
8462 "Template for directive '{0}' must have exactly one root element. {1}",
8463 origAsyncDirective.name, templateUrl);
8464 }
8465
8466 tempTemplateAttrs = {$attr: {}};
8467 replaceWith($rootElement, $compileNode, compileNode);
8468 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8469
8470 if (isObject(origAsyncDirective.scope)) {
8471 markDirectivesAsIsolate(templateDirectives);
8472 }
8473 directives = templateDirectives.concat(directives);
8474 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8475 } else {
8476 compileNode = beforeTemplateCompileNode;
8477 $compileNode.html(content);
8478 }
8479
8480 directives.unshift(derivedSyncDirective);
8481
8482 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8483 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8484 previousCompileContext);
8485 forEach($rootElement, function(node, i) {
8486 if (node == compileNode) {
8487 $rootElement[i] = $compileNode[0];
8488 }
8489 });
8490 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8491
8492 while (linkQueue.length) {
8493 var scope = linkQueue.shift(),
8494 beforeTemplateLinkNode = linkQueue.shift(),
8495 linkRootElement = linkQueue.shift(),
8496 boundTranscludeFn = linkQueue.shift(),
8497 linkNode = $compileNode[0];
8498
8499 if (scope.$$destroyed) continue;
8500
8501 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8502 var oldClasses = beforeTemplateLinkNode.className;
8503
8504 if (!(previousCompileContext.hasElementTranscludeDirective &&
8505 origAsyncDirective.replace)) {
8506 // it was cloned therefore we have to clone as well.
8507 linkNode = jqLiteClone(compileNode);
8508 }
8509 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8510
8511 // Copy in CSS classes from original node
8512 safeAddClass(jqLite(linkNode), oldClasses);
8513 }
8514 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8515 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8516 } else {
8517 childBoundTranscludeFn = boundTranscludeFn;
8518 }
8519 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8520 childBoundTranscludeFn, afterTemplateNodeLinkFn);
8521 }
8522 linkQueue = null;
8523 });
8524
8525 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8526 var childBoundTranscludeFn = boundTranscludeFn;
8527 if (scope.$$destroyed) return;
8528 if (linkQueue) {
8529 linkQueue.push(scope,
8530 node,
8531 rootElement,
8532 childBoundTranscludeFn);
8533 } else {
8534 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8535 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8536 }
8537 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
8538 afterTemplateNodeLinkFn);
8539 }
8540 };
8541 }
8542
8543
8544 /**
8545 * Sorting function for bound directives.
8546 */
8547 function byPriority(a, b) {
8548 var diff = b.priority - a.priority;
8549 if (diff !== 0) return diff;
8550 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8551 return a.index - b.index;
8552 }
8553
8554 function assertNoDuplicate(what, previousDirective, directive, element) {
8555
8556 function wrapModuleNameIfDefined(moduleName) {
8557 return moduleName ?
8558 (' (module: ' + moduleName + ')') :
8559 '';
8560 }
8561
8562 if (previousDirective) {
8563 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8564 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8565 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8566 }
8567 }
8568
8569
8570 function addTextInterpolateDirective(directives, text) {
8571 var interpolateFn = $interpolate(text, true);
8572 if (interpolateFn) {
8573 directives.push({
8574 priority: 0,
8575 compile: function textInterpolateCompileFn(templateNode) {
8576 var templateNodeParent = templateNode.parent(),
8577 hasCompileParent = !!templateNodeParent.length;
8578
8579 // When transcluding a template that has bindings in the root
8580 // we don't have a parent and thus need to add the class during linking fn.
8581 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8582
8583 return function textInterpolateLinkFn(scope, node) {
8584 var parent = node.parent();
8585 if (!hasCompileParent) compile.$$addBindingClass(parent);
8586 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8587 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8588 node[0].nodeValue = value;
8589 });
8590 };
8591 }
8592 });
8593 }
8594 }
8595
8596
8597 function wrapTemplate(type, template) {
8598 type = lowercase(type || 'html');
8599 switch (type) {
8600 case 'svg':
8601 case 'math':
8602 var wrapper = document.createElement('div');
8603 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8604 return wrapper.childNodes[0].childNodes;
8605 default:
8606 return template;
8607 }
8608 }
8609
8610
8611 function getTrustedContext(node, attrNormalizedName) {
8612 if (attrNormalizedName == "srcdoc") {
8613 return $sce.HTML;
8614 }
8615 var tag = nodeName_(node);
8616 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8617 if (attrNormalizedName == "xlinkHref" ||
8618 (tag == "form" && attrNormalizedName == "action") ||
8619 (tag != "img" && (attrNormalizedName == "src" ||
8620 attrNormalizedName == "ngSrc"))) {
8621 return $sce.RESOURCE_URL;
8622 }
8623 }
8624
8625
8626 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8627 var trustedContext = getTrustedContext(node, name);
8628 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8629
8630 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8631
8632 // no interpolation found -> ignore
8633 if (!interpolateFn) return;
8634
8635
8636 if (name === "multiple" && nodeName_(node) === "select") {
8637 throw $compileMinErr("selmulti",
8638 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8639 startingTag(node));
8640 }
8641
8642 directives.push({
8643 priority: 100,
8644 compile: function() {
8645 return {
8646 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8647 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8648
8649 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8650 throw $compileMinErr('nodomevents',
8651 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8652 "ng- versions (such as ng-click instead of onclick) instead.");
8653 }
8654
8655 // If the attribute has changed since last $interpolate()ed
8656 var newValue = attr[name];
8657 if (newValue !== value) {
8658 // we need to interpolate again since the attribute value has been updated
8659 // (e.g. by another directive's compile function)
8660 // ensure unset/empty values make interpolateFn falsy
8661 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8662 value = newValue;
8663 }
8664
8665 // if attribute was updated so that there is no interpolation going on we don't want to
8666 // register any observers
8667 if (!interpolateFn) return;
8668
8669 // initialize attr object so that it's ready in case we need the value for isolate
8670 // scope initialization, otherwise the value would not be available from isolate
8671 // directive's linking fn during linking phase
8672 attr[name] = interpolateFn(scope);
8673
8674 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8675 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8676 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8677 //special case for class attribute addition + removal
8678 //so that class changes can tap into the animation
8679 //hooks provided by the $animate service. Be sure to
8680 //skip animations when the first digest occurs (when
8681 //both the new and the old values are the same) since
8682 //the CSS classes are the non-interpolated values
8683 if (name === 'class' && newValue != oldValue) {
8684 attr.$updateClass(newValue, oldValue);
8685 } else {
8686 attr.$set(name, newValue);
8687 }
8688 });
8689 }
8690 };
8691 }
8692 });
8693 }
8694
8695
8696 /**
8697 * This is a special jqLite.replaceWith, which can replace items which
8698 * have no parents, provided that the containing jqLite collection is provided.
8699 *
8700 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8701 * in the root of the tree.
8702 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8703 * the shell, but replace its DOM node reference.
8704 * @param {Node} newNode The new DOM node.
8705 */
8706 function replaceWith($rootElement, elementsToRemove, newNode) {
8707 var firstElementToRemove = elementsToRemove[0],
8708 removeCount = elementsToRemove.length,
8709 parent = firstElementToRemove.parentNode,
8710 i, ii;
8711
8712 if ($rootElement) {
8713 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8714 if ($rootElement[i] == firstElementToRemove) {
8715 $rootElement[i++] = newNode;
8716 for (var j = i, j2 = j + removeCount - 1,
8717 jj = $rootElement.length;
8718 j < jj; j++, j2++) {
8719 if (j2 < jj) {
8720 $rootElement[j] = $rootElement[j2];
8721 } else {
8722 delete $rootElement[j];
8723 }
8724 }
8725 $rootElement.length -= removeCount - 1;
8726
8727 // If the replaced element is also the jQuery .context then replace it
8728 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8729 // http://api.jquery.com/context/
8730 if ($rootElement.context === firstElementToRemove) {
8731 $rootElement.context = newNode;
8732 }
8733 break;
8734 }
8735 }
8736 }
8737
8738 if (parent) {
8739 parent.replaceChild(newNode, firstElementToRemove);
8740 }
8741
8742 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8743 var fragment = document.createDocumentFragment();
8744 fragment.appendChild(firstElementToRemove);
8745
8746 if (jqLite.hasData(firstElementToRemove)) {
8747 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8748 // data here because there's no public interface in jQuery to do that and copying over
8749 // event listeners (which is the main use of private data) wouldn't work anyway.
8750 jqLite(newNode).data(jqLite(firstElementToRemove).data());
8751
8752 // Remove data of the replaced element. We cannot just call .remove()
8753 // on the element it since that would deallocate scope that is needed
8754 // for the new node. Instead, remove the data "manually".
8755 if (!jQuery) {
8756 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8757 } else {
8758 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8759 // the replaced element. The cleanData version monkey-patched by Angular would cause
8760 // the scope to be trashed and we do need the very same scope to work with the new
8761 // element. However, we cannot just cache the non-patched version and use it here as
8762 // that would break if another library patches the method after Angular does (one
8763 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8764 // skipped this one time.
8765 skipDestroyOnNextJQueryCleanData = true;
8766 jQuery.cleanData([firstElementToRemove]);
8767 }
8768 }
8769
8770 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8771 var element = elementsToRemove[k];
8772 jqLite(element).remove(); // must do this way to clean up expando
8773 fragment.appendChild(element);
8774 delete elementsToRemove[k];
8775 }
8776
8777 elementsToRemove[0] = newNode;
8778 elementsToRemove.length = 1;
8779 }
8780
8781
8782 function cloneAndAnnotateFn(fn, annotation) {
8783 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8784 }
8785
8786
8787 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8788 try {
8789 linkFn(scope, $element, attrs, controllers, transcludeFn);
8790 } catch (e) {
8791 $exceptionHandler(e, startingTag($element));
8792 }
8793 }
8794
8795
8796 // Set up $watches for isolate scope and controller bindings. This process
8797 // only occurs for isolate scopes and new scopes with controllerAs.
8798 function initializeDirectiveBindings(scope, attrs, destination, bindings,
8799 directive, newScope) {
8800 var onNewScopeDestroyed;
8801 forEach(bindings, function(definition, scopeName) {
8802 var attrName = definition.attrName,
8803 optional = definition.optional,
8804 mode = definition.mode, // @, =, or &
8805 lastValue,
8806 parentGet, parentSet, compare;
8807
8808 switch (mode) {
8809
8810 case '@':
8811 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8812 destination[scopeName] = attrs[attrName] = void 0;
8813 }
8814 attrs.$observe(attrName, function(value) {
8815 if (isString(value)) {
8816 destination[scopeName] = value;
8817 }
8818 });
8819 attrs.$$observers[attrName].$$scope = scope;
8820 if (isString(attrs[attrName])) {
8821 // If the attribute has been provided then we trigger an interpolation to ensure
8822 // the value is there for use in the link fn
8823 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8824 }
8825 break;
8826
8827 case '=':
8828 if (!hasOwnProperty.call(attrs, attrName)) {
8829 if (optional) break;
8830 attrs[attrName] = void 0;
8831 }
8832 if (optional && !attrs[attrName]) break;
8833
8834 parentGet = $parse(attrs[attrName]);
8835 if (parentGet.literal) {
8836 compare = equals;
8837 } else {
8838 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8839 }
8840 parentSet = parentGet.assign || function() {
8841 // reset the change, or we will throw this exception on every $digest
8842 lastValue = destination[scopeName] = parentGet(scope);
8843 throw $compileMinErr('nonassign',
8844 "Expression '{0}' used with directive '{1}' is non-assignable!",
8845 attrs[attrName], directive.name);
8846 };
8847 lastValue = destination[scopeName] = parentGet(scope);
8848 var parentValueWatch = function parentValueWatch(parentValue) {
8849 if (!compare(parentValue, destination[scopeName])) {
8850 // we are out of sync and need to copy
8851 if (!compare(parentValue, lastValue)) {
8852 // parent changed and it has precedence
8853 destination[scopeName] = parentValue;
8854 } else {
8855 // if the parent can be assigned then do so
8856 parentSet(scope, parentValue = destination[scopeName]);
8857 }
8858 }
8859 return lastValue = parentValue;
8860 };
8861 parentValueWatch.$stateful = true;
8862 var unwatch;
8863 if (definition.collection) {
8864 unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8865 } else {
8866 unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8867 }
8868 onNewScopeDestroyed = (onNewScopeDestroyed || []);
8869 onNewScopeDestroyed.push(unwatch);
8870 break;
8871
8872 case '&':
8873 // Don't assign Object.prototype method to scope
8874 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
8875
8876 // Don't assign noop to destination if expression is not valid
8877 if (parentGet === noop && optional) break;
8878
8879 destination[scopeName] = function(locals) {
8880 return parentGet(scope, locals);
8881 };
8882 break;
8883 }
8884 });
8885 var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
8886 for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
8887 onNewScopeDestroyed[i]();
8888 }
8889 } : noop;
8890 if (newScope && destroyBindings !== noop) {
8891 newScope.$on('$destroy', destroyBindings);
8892 return noop;
8893 }
8894 return destroyBindings;
8895 }
8896 }];
8897}
8898
8899var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
8900/**
8901 * Converts all accepted directives format into proper directive name.
8902 * @param name Name to normalize
8903 */
8904function directiveNormalize(name) {
8905 return camelCase(name.replace(PREFIX_REGEXP, ''));
8906}
8907
8908/**
8909 * @ngdoc type
8910 * @name $compile.directive.Attributes
8911 *
8912 * @description
8913 * A shared object between directive compile / linking functions which contains normalized DOM
8914 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
8915 * needed since all of these are treated as equivalent in Angular:
8916 *
8917 * ```
8918 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
8919 * ```
8920 */
8921
8922/**
8923 * @ngdoc property
8924 * @name $compile.directive.Attributes#$attr
8925 *
8926 * @description
8927 * A map of DOM element attribute names to the normalized name. This is
8928 * needed to do reverse lookup from normalized name back to actual name.
8929 */
8930
8931
8932/**
8933 * @ngdoc method
8934 * @name $compile.directive.Attributes#$set
8935 * @kind function
8936 *
8937 * @description
8938 * Set DOM element attribute value.
8939 *
8940 *
8941 * @param {string} name Normalized element attribute name of the property to modify. The name is
8942 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
8943 * property to the original name.
8944 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
8945 */
8946
8947
8948
8949/**
8950 * Closure compiler type information
8951 */
8952
8953function nodesetLinkingFn(
8954 /* angular.Scope */ scope,
8955 /* NodeList */ nodeList,
8956 /* Element */ rootElement,
8957 /* function(Function) */ boundTranscludeFn
8958) {}
8959
8960function directiveLinkingFn(
8961 /* nodesetLinkingFn */ nodesetLinkingFn,
8962 /* angular.Scope */ scope,
8963 /* Node */ node,
8964 /* Element */ rootElement,
8965 /* function(Function) */ boundTranscludeFn
8966) {}
8967
8968function tokenDifference(str1, str2) {
8969 var values = '',
8970 tokens1 = str1.split(/\s+/),
8971 tokens2 = str2.split(/\s+/);
8972
8973 outer:
8974 for (var i = 0; i < tokens1.length; i++) {
8975 var token = tokens1[i];
8976 for (var j = 0; j < tokens2.length; j++) {
8977 if (token == tokens2[j]) continue outer;
8978 }
8979 values += (values.length > 0 ? ' ' : '') + token;
8980 }
8981 return values;
8982}
8983
8984function removeComments(jqNodes) {
8985 jqNodes = jqLite(jqNodes);
8986 var i = jqNodes.length;
8987
8988 if (i <= 1) {
8989 return jqNodes;
8990 }
8991
8992 while (i--) {
8993 var node = jqNodes[i];
8994 if (node.nodeType === NODE_TYPE_COMMENT) {
8995 splice.call(jqNodes, i, 1);
8996 }
8997 }
8998 return jqNodes;
8999}
9000
9001var $controllerMinErr = minErr('$controller');
9002
9003
9004var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9005function identifierForController(controller, ident) {
9006 if (ident && isString(ident)) return ident;
9007 if (isString(controller)) {
9008 var match = CNTRL_REG.exec(controller);
9009 if (match) return match[3];
9010 }
9011}
9012
9013
9014/**
9015 * @ngdoc provider
9016 * @name $controllerProvider
9017 * @description
9018 * The {@link ng.$controller $controller service} is used by Angular to create new
9019 * controllers.
9020 *
9021 * This provider allows controller registration via the
9022 * {@link ng.$controllerProvider#register register} method.
9023 */
9024function $ControllerProvider() {
9025 var controllers = {},
9026 globals = false;
9027
9028 /**
9029 * @ngdoc method
9030 * @name $controllerProvider#register
9031 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9032 * the names and the values are the constructors.
9033 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9034 * annotations in the array notation).
9035 */
9036 this.register = function(name, constructor) {
9037 assertNotHasOwnProperty(name, 'controller');
9038 if (isObject(name)) {
9039 extend(controllers, name);
9040 } else {
9041 controllers[name] = constructor;
9042 }
9043 };
9044
9045 /**
9046 * @ngdoc method
9047 * @name $controllerProvider#allowGlobals
9048 * @description If called, allows `$controller` to find controller constructors on `window`
9049 */
9050 this.allowGlobals = function() {
9051 globals = true;
9052 };
9053
9054
9055 this.$get = ['$injector', '$window', function($injector, $window) {
9056
9057 /**
9058 * @ngdoc service
9059 * @name $controller
9060 * @requires $injector
9061 *
9062 * @param {Function|string} constructor If called with a function then it's considered to be the
9063 * controller constructor function. Otherwise it's considered to be a string which is used
9064 * to retrieve the controller constructor using the following steps:
9065 *
9066 * * check if a controller with given name is registered via `$controllerProvider`
9067 * * check if evaluating the string on the current scope returns a constructor
9068 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9069 * `window` object (not recommended)
9070 *
9071 * The string can use the `controller as property` syntax, where the controller instance is published
9072 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9073 * to work correctly.
9074 *
9075 * @param {Object} locals Injection locals for Controller.
9076 * @return {Object} Instance of given controller.
9077 *
9078 * @description
9079 * `$controller` service is responsible for instantiating controllers.
9080 *
9081 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9082 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9083 */
9084 return function(expression, locals, later, ident) {
9085 // PRIVATE API:
9086 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9087 // If true, $controller will allocate the object with the correct
9088 // prototype chain, but will not invoke the controller until a returned
9089 // callback is invoked.
9090 // param `ident` --- An optional label which overrides the label parsed from the controller
9091 // expression, if any.
9092 var instance, match, constructor, identifier;
9093 later = later === true;
9094 if (ident && isString(ident)) {
9095 identifier = ident;
9096 }
9097
9098 if (isString(expression)) {
9099 match = expression.match(CNTRL_REG);
9100 if (!match) {
9101 throw $controllerMinErr('ctrlfmt',
9102 "Badly formed controller string '{0}'. " +
9103 "Must match `__name__ as __id__` or `__name__`.", expression);
9104 }
9105 constructor = match[1],
9106 identifier = identifier || match[3];
9107 expression = controllers.hasOwnProperty(constructor)
9108 ? controllers[constructor]
9109 : getter(locals.$scope, constructor, true) ||
9110 (globals ? getter($window, constructor, true) : undefined);
9111
9112 assertArgFn(expression, constructor, true);
9113 }
9114
9115 if (later) {
9116 // Instantiate controller later:
9117 // This machinery is used to create an instance of the object before calling the
9118 // controller's constructor itself.
9119 //
9120 // This allows properties to be added to the controller before the constructor is
9121 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9122 //
9123 // This feature is not intended for use by applications, and is thus not documented
9124 // publicly.
9125 // Object creation: http://jsperf.com/create-constructor/2
9126 var controllerPrototype = (isArray(expression) ?
9127 expression[expression.length - 1] : expression).prototype;
9128 instance = Object.create(controllerPrototype || null);
9129
9130 if (identifier) {
9131 addIdentifier(locals, identifier, instance, constructor || expression.name);
9132 }
9133
9134 var instantiate;
9135 return instantiate = extend(function() {
9136 var result = $injector.invoke(expression, instance, locals, constructor);
9137 if (result !== instance && (isObject(result) || isFunction(result))) {
9138 instance = result;
9139 if (identifier) {
9140 // If result changed, re-assign controllerAs value to scope.
9141 addIdentifier(locals, identifier, instance, constructor || expression.name);
9142 }
9143 }
9144 return instance;
9145 }, {
9146 instance: instance,
9147 identifier: identifier
9148 });
9149 }
9150
9151 instance = $injector.instantiate(expression, locals, constructor);
9152
9153 if (identifier) {
9154 addIdentifier(locals, identifier, instance, constructor || expression.name);
9155 }
9156
9157 return instance;
9158 };
9159
9160 function addIdentifier(locals, identifier, instance, name) {
9161 if (!(locals && isObject(locals.$scope))) {
9162 throw minErr('$controller')('noscp',
9163 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9164 name, identifier);
9165 }
9166
9167 locals.$scope[identifier] = instance;
9168 }
9169 }];
9170}
9171
9172/**
9173 * @ngdoc service
9174 * @name $document
9175 * @requires $window
9176 *
9177 * @description
9178 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9179 *
9180 * @example
9181 <example module="documentExample">
9182 <file name="index.html">
9183 <div ng-controller="ExampleController">
9184 <p>$document title: <b ng-bind="title"></b></p>
9185 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9186 </div>
9187 </file>
9188 <file name="script.js">
9189 angular.module('documentExample', [])
9190 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9191 $scope.title = $document[0].title;
9192 $scope.windowTitle = angular.element(window.document)[0].title;
9193 }]);
9194 </file>
9195 </example>
9196 */
9197function $DocumentProvider() {
9198 this.$get = ['$window', function(window) {
9199 return jqLite(window.document);
9200 }];
9201}
9202
9203/**
9204 * @ngdoc service
9205 * @name $exceptionHandler
9206 * @requires ng.$log
9207 *
9208 * @description
9209 * Any uncaught exception in angular expressions is delegated to this service.
9210 * The default implementation simply delegates to `$log.error` which logs it into
9211 * the browser console.
9212 *
9213 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9214 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9215 *
9216 * ## Example:
9217 *
9218 * ```js
9219 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9220 * return function(exception, cause) {
9221 * exception.message += ' (caused by "' + cause + '")';
9222 * throw exception;
9223 * };
9224 * });
9225 * ```
9226 *
9227 * This example will override the normal action of `$exceptionHandler`, to make angular
9228 * exceptions fail hard when they happen, instead of just logging to the console.
9229 *
9230 * <hr />
9231 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9232 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9233 * (unless executed during a digest).
9234 *
9235 * If you wish, you can manually delegate exceptions, e.g.
9236 * `try { ... } catch(e) { $exceptionHandler(e); }`
9237 *
9238 * @param {Error} exception Exception associated with the error.
9239 * @param {string=} cause optional information about the context in which
9240 * the error was thrown.
9241 *
9242 */
9243function $ExceptionHandlerProvider() {
9244 this.$get = ['$log', function($log) {
9245 return function(exception, cause) {
9246 $log.error.apply($log, arguments);
9247 };
9248 }];
9249}
9250
9251var $$ForceReflowProvider = function() {
9252 this.$get = ['$document', function($document) {
9253 return function(domNode) {
9254 //the line below will force the browser to perform a repaint so
9255 //that all the animated elements within the animation frame will
9256 //be properly updated and drawn on screen. This is required to
9257 //ensure that the preparation animation is properly flushed so that
9258 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9259 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9260 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9261 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9262 if (domNode) {
9263 if (!domNode.nodeType && domNode instanceof jqLite) {
9264 domNode = domNode[0];
9265 }
9266 } else {
9267 domNode = $document[0].body;
9268 }
9269 return domNode.offsetWidth + 1;
9270 };
9271 }];
9272};
9273
9274var APPLICATION_JSON = 'application/json';
9275var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9276var JSON_START = /^\[|^\{(?!\{)/;
9277var JSON_ENDS = {
9278 '[': /]$/,
9279 '{': /}$/
9280};
9281var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9282var $httpMinErr = minErr('$http');
9283var $httpMinErrLegacyFn = function(method) {
9284 return function() {
9285 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9286 };
9287};
9288
9289function serializeValue(v) {
9290 if (isObject(v)) {
9291 return isDate(v) ? v.toISOString() : toJson(v);
9292 }
9293 return v;
9294}
9295
9296
9297function $HttpParamSerializerProvider() {
9298 /**
9299 * @ngdoc service
9300 * @name $httpParamSerializer
9301 * @description
9302 *
9303 * Default {@link $http `$http`} params serializer that converts objects to strings
9304 * according to the following rules:
9305 *
9306 * * `{'foo': 'bar'}` results in `foo=bar`
9307 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9308 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9309 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9310 *
9311 * Note that serializer will sort the request parameters alphabetically.
9312 * */
9313
9314 this.$get = function() {
9315 return function ngParamSerializer(params) {
9316 if (!params) return '';
9317 var parts = [];
9318 forEachSorted(params, function(value, key) {
9319 if (value === null || isUndefined(value)) return;
9320 if (isArray(value)) {
9321 forEach(value, function(v, k) {
9322 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9323 });
9324 } else {
9325 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9326 }
9327 });
9328
9329 return parts.join('&');
9330 };
9331 };
9332}
9333
9334function $HttpParamSerializerJQLikeProvider() {
9335 /**
9336 * @ngdoc service
9337 * @name $httpParamSerializerJQLike
9338 * @description
9339 *
9340 * Alternative {@link $http `$http`} params serializer that follows
9341 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9342 * The serializer will also sort the params alphabetically.
9343 *
9344 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9345 *
9346 * ```js
9347 * $http({
9348 * url: myUrl,
9349 * method: 'GET',
9350 * params: myParams,
9351 * paramSerializer: '$httpParamSerializerJQLike'
9352 * });
9353 * ```
9354 *
9355 * It is also possible to set it as the default `paramSerializer` in the
9356 * {@link $httpProvider#defaults `$httpProvider`}.
9357 *
9358 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9359 * form data for submission:
9360 *
9361 * ```js
9362 * .controller(function($http, $httpParamSerializerJQLike) {
9363 * //...
9364 *
9365 * $http({
9366 * url: myUrl,
9367 * method: 'POST',
9368 * data: $httpParamSerializerJQLike(myData),
9369 * headers: {
9370 * 'Content-Type': 'application/x-www-form-urlencoded'
9371 * }
9372 * });
9373 *
9374 * });
9375 * ```
9376 *
9377 * */
9378 this.$get = function() {
9379 return function jQueryLikeParamSerializer(params) {
9380 if (!params) return '';
9381 var parts = [];
9382 serialize(params, '', true);
9383 return parts.join('&');
9384
9385 function serialize(toSerialize, prefix, topLevel) {
9386 if (toSerialize === null || isUndefined(toSerialize)) return;
9387 if (isArray(toSerialize)) {
9388 forEach(toSerialize, function(value, index) {
9389 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9390 });
9391 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9392 forEachSorted(toSerialize, function(value, key) {
9393 serialize(value, prefix +
9394 (topLevel ? '' : '[') +
9395 key +
9396 (topLevel ? '' : ']'));
9397 });
9398 } else {
9399 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9400 }
9401 }
9402 };
9403 };
9404}
9405
9406function defaultHttpResponseTransform(data, headers) {
9407 if (isString(data)) {
9408 // Strip json vulnerability protection prefix and trim whitespace
9409 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9410
9411 if (tempData) {
9412 var contentType = headers('Content-Type');
9413 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9414 data = fromJson(tempData);
9415 }
9416 }
9417 }
9418
9419 return data;
9420}
9421
9422function isJsonLike(str) {
9423 var jsonStart = str.match(JSON_START);
9424 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9425}
9426
9427/**
9428 * Parse headers into key value object
9429 *
9430 * @param {string} headers Raw headers as a string
9431 * @returns {Object} Parsed headers as key value object
9432 */
9433function parseHeaders(headers) {
9434 var parsed = createMap(), i;
9435
9436 function fillInParsed(key, val) {
9437 if (key) {
9438 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9439 }
9440 }
9441
9442 if (isString(headers)) {
9443 forEach(headers.split('\n'), function(line) {
9444 i = line.indexOf(':');
9445 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9446 });
9447 } else if (isObject(headers)) {
9448 forEach(headers, function(headerVal, headerKey) {
9449 fillInParsed(lowercase(headerKey), trim(headerVal));
9450 });
9451 }
9452
9453 return parsed;
9454}
9455
9456
9457/**
9458 * Returns a function that provides access to parsed headers.
9459 *
9460 * Headers are lazy parsed when first requested.
9461 * @see parseHeaders
9462 *
9463 * @param {(string|Object)} headers Headers to provide access to.
9464 * @returns {function(string=)} Returns a getter function which if called with:
9465 *
9466 * - if called with single an argument returns a single header value or null
9467 * - if called with no arguments returns an object containing all headers.
9468 */
9469function headersGetter(headers) {
9470 var headersObj;
9471
9472 return function(name) {
9473 if (!headersObj) headersObj = parseHeaders(headers);
9474
9475 if (name) {
9476 var value = headersObj[lowercase(name)];
9477 if (value === void 0) {
9478 value = null;
9479 }
9480 return value;
9481 }
9482
9483 return headersObj;
9484 };
9485}
9486
9487
9488/**
9489 * Chain all given functions
9490 *
9491 * This function is used for both request and response transforming
9492 *
9493 * @param {*} data Data to transform.
9494 * @param {function(string=)} headers HTTP headers getter fn.
9495 * @param {number} status HTTP status code of the response.
9496 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9497 * @returns {*} Transformed data.
9498 */
9499function transformData(data, headers, status, fns) {
9500 if (isFunction(fns)) {
9501 return fns(data, headers, status);
9502 }
9503
9504 forEach(fns, function(fn) {
9505 data = fn(data, headers, status);
9506 });
9507
9508 return data;
9509}
9510
9511
9512function isSuccess(status) {
9513 return 200 <= status && status < 300;
9514}
9515
9516
9517/**
9518 * @ngdoc provider
9519 * @name $httpProvider
9520 * @description
9521 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9522 * */
9523function $HttpProvider() {
9524 /**
9525 * @ngdoc property
9526 * @name $httpProvider#defaults
9527 * @description
9528 *
9529 * Object containing default values for all {@link ng.$http $http} requests.
9530 *
9531 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9532 * that will provide the cache for all requests who set their `cache` property to `true`.
9533 * If you set the `defaults.cache = false` then only requests that specify their own custom
9534 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9535 *
9536 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9537 * Defaults value is `'XSRF-TOKEN'`.
9538 *
9539 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9540 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9541 *
9542 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9543 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9544 * setting default headers.
9545 * - **`defaults.headers.common`**
9546 * - **`defaults.headers.post`**
9547 * - **`defaults.headers.put`**
9548 * - **`defaults.headers.patch`**
9549 *
9550 *
9551 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9552 * used to the prepare string representation of request parameters (specified as an object).
9553 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9554 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9555 *
9556 **/
9557 var defaults = this.defaults = {
9558 // transform incoming response data
9559 transformResponse: [defaultHttpResponseTransform],
9560
9561 // transform outgoing request data
9562 transformRequest: [function(d) {
9563 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9564 }],
9565
9566 // default headers
9567 headers: {
9568 common: {
9569 'Accept': 'application/json, text/plain, */*'
9570 },
9571 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9572 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9573 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9574 },
9575
9576 xsrfCookieName: 'XSRF-TOKEN',
9577 xsrfHeaderName: 'X-XSRF-TOKEN',
9578
9579 paramSerializer: '$httpParamSerializer'
9580 };
9581
9582 var useApplyAsync = false;
9583 /**
9584 * @ngdoc method
9585 * @name $httpProvider#useApplyAsync
9586 * @description
9587 *
9588 * Configure $http service to combine processing of multiple http responses received at around
9589 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9590 * significant performance improvement for bigger applications that make many HTTP requests
9591 * concurrently (common during application bootstrap).
9592 *
9593 * Defaults to false. If no value is specified, returns the current configured value.
9594 *
9595 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9596 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9597 * to load and share the same digest cycle.
9598 *
9599 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9600 * otherwise, returns the current configured value.
9601 **/
9602 this.useApplyAsync = function(value) {
9603 if (isDefined(value)) {
9604 useApplyAsync = !!value;
9605 return this;
9606 }
9607 return useApplyAsync;
9608 };
9609
9610 var useLegacyPromise = true;
9611 /**
9612 * @ngdoc method
9613 * @name $httpProvider#useLegacyPromiseExtensions
9614 * @description
9615 *
9616 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9617 * This should be used to make sure that applications work without these methods.
9618 *
9619 * Defaults to false. If no value is specified, returns the current configured value.
9620 *
9621 * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods.
9622 *
9623 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9624 * otherwise, returns the current configured value.
9625 **/
9626 this.useLegacyPromiseExtensions = function(value) {
9627 if (isDefined(value)) {
9628 useLegacyPromise = !!value;
9629 return this;
9630 }
9631 return useLegacyPromise;
9632 };
9633
9634 /**
9635 * @ngdoc property
9636 * @name $httpProvider#interceptors
9637 * @description
9638 *
9639 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9640 * pre-processing of request or postprocessing of responses.
9641 *
9642 * These service factories are ordered by request, i.e. they are applied in the same order as the
9643 * array, on request, but reverse order, on response.
9644 *
9645 * {@link ng.$http#interceptors Interceptors detailed info}
9646 **/
9647 var interceptorFactories = this.interceptors = [];
9648
9649 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9650 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9651
9652 var defaultCache = $cacheFactory('$http');
9653
9654 /**
9655 * Make sure that default param serializer is exposed as a function
9656 */
9657 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9658 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9659
9660 /**
9661 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9662 * The reversal is needed so that we can build up the interception chain around the
9663 * server request.
9664 */
9665 var reversedInterceptors = [];
9666
9667 forEach(interceptorFactories, function(interceptorFactory) {
9668 reversedInterceptors.unshift(isString(interceptorFactory)
9669 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9670 });
9671
9672 /**
9673 * @ngdoc service
9674 * @kind function
9675 * @name $http
9676 * @requires ng.$httpBackend
9677 * @requires $cacheFactory
9678 * @requires $rootScope
9679 * @requires $q
9680 * @requires $injector
9681 *
9682 * @description
9683 * The `$http` service is a core Angular service that facilitates communication with the remote
9684 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9685 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9686 *
9687 * For unit testing applications that use `$http` service, see
9688 * {@link ngMock.$httpBackend $httpBackend mock}.
9689 *
9690 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9691 * $resource} service.
9692 *
9693 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9694 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9695 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9696 *
9697 *
9698 * ## General usage
9699 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9700 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9701 *
9702 * ```js
9703 * // Simple GET request example:
9704 * $http({
9705 * method: 'GET',
9706 * url: '/someUrl'
9707 * }).then(function successCallback(response) {
9708 * // this callback will be called asynchronously
9709 * // when the response is available
9710 * }, function errorCallback(response) {
9711 * // called asynchronously if an error occurs
9712 * // or server returns response with an error status.
9713 * });
9714 * ```
9715 *
9716 * The response object has these properties:
9717 *
9718 * - **data** – `{string|Object}` – The response body transformed with the transform
9719 * functions.
9720 * - **status** – `{number}` – HTTP status code of the response.
9721 * - **headers** – `{function([headerName])}` – Header getter function.
9722 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9723 * - **statusText** – `{string}` – HTTP status text of the response.
9724 *
9725 * A response status code between 200 and 299 is considered a success status and
9726 * will result in the success callback being called. Note that if the response is a redirect,
9727 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9728 * called for such responses.
9729 *
9730 *
9731 * ## Shortcut methods
9732 *
9733 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9734 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9735 * last argument.
9736 *
9737 * ```js
9738 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9739 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9740 * ```
9741 *
9742 * Complete list of shortcut methods:
9743 *
9744 * - {@link ng.$http#get $http.get}
9745 * - {@link ng.$http#head $http.head}
9746 * - {@link ng.$http#post $http.post}
9747 * - {@link ng.$http#put $http.put}
9748 * - {@link ng.$http#delete $http.delete}
9749 * - {@link ng.$http#jsonp $http.jsonp}
9750 * - {@link ng.$http#patch $http.patch}
9751 *
9752 *
9753 * ## Writing Unit Tests that use $http
9754 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9755 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9756 * request using trained responses.
9757 *
9758 * ```
9759 * $httpBackend.expectGET(...);
9760 * $http.get(...);
9761 * $httpBackend.flush();
9762 * ```
9763 *
9764 * ## Deprecation Notice
9765 * <div class="alert alert-danger">
9766 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9767 * Use the standard `then` method instead.
9768 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9769 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9770 * </div>
9771 *
9772 * ## Setting HTTP Headers
9773 *
9774 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9775 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9776 * object, which currently contains this default configuration:
9777 *
9778 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9779 * - `Accept: application/json, text/plain, * / *`
9780 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9781 * - `Content-Type: application/json`
9782 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9783 * - `Content-Type: application/json`
9784 *
9785 * To add or overwrite these defaults, simply add or remove a property from these configuration
9786 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9787 * with the lowercased HTTP method name as the key, e.g.
9788 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9789 *
9790 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9791 * fashion. For example:
9792 *
9793 * ```
9794 * module.run(function($http) {
9795 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9796 * });
9797 * ```
9798 *
9799 * In addition, you can supply a `headers` property in the config object passed when
9800 * calling `$http(config)`, which overrides the defaults without changing them globally.
9801 *
9802 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9803 * Use the `headers` property, setting the desired header to `undefined`. For example:
9804 *
9805 * ```js
9806 * var req = {
9807 * method: 'POST',
9808 * url: 'http://example.com',
9809 * headers: {
9810 * 'Content-Type': undefined
9811 * },
9812 * data: { test: 'test' }
9813 * }
9814 *
9815 * $http(req).then(function(){...}, function(){...});
9816 * ```
9817 *
9818 * ## Transforming Requests and Responses
9819 *
9820 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9821 * and `transformResponse`. These properties can be a single function that returns
9822 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9823 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9824 *
9825 * ### Default Transformations
9826 *
9827 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9828 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9829 * then these will be applied.
9830 *
9831 * You can augment or replace the default transformations by modifying these properties by adding to or
9832 * replacing the array.
9833 *
9834 * Angular provides the following default transformations:
9835 *
9836 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9837 *
9838 * - If the `data` property of the request configuration object contains an object, serialize it
9839 * into JSON format.
9840 *
9841 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9842 *
9843 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9844 * - If JSON response is detected, deserialize it using a JSON parser.
9845 *
9846 *
9847 * ### Overriding the Default Transformations Per Request
9848 *
9849 * If you wish override the request/response transformations only for a single request then provide
9850 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9851 * into `$http`.
9852 *
9853 * Note that if you provide these properties on the config object the default transformations will be
9854 * overwritten. If you wish to augment the default transformations then you must include them in your
9855 * local transformation array.
9856 *
9857 * The following code demonstrates adding a new response transformation to be run after the default response
9858 * transformations have been run.
9859 *
9860 * ```js
9861 * function appendTransform(defaults, transform) {
9862 *
9863 * // We can't guarantee that the default transformation is an array
9864 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9865 *
9866 * // Append the new transformation to the defaults
9867 * return defaults.concat(transform);
9868 * }
9869 *
9870 * $http({
9871 * url: '...',
9872 * method: 'GET',
9873 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9874 * return doTransform(value);
9875 * })
9876 * });
9877 * ```
9878 *
9879 *
9880 * ## Caching
9881 *
9882 * To enable caching, set the request configuration `cache` property to `true` (to use default
9883 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
9884 * When the cache is enabled, `$http` stores the response from the server in the specified
9885 * cache. The next time the same request is made, the response is served from the cache without
9886 * sending a request to the server.
9887 *
9888 * Note that even if the response is served from cache, delivery of the data is asynchronous in
9889 * the same way that real requests are.
9890 *
9891 * If there are multiple GET requests for the same URL that should be cached using the same
9892 * cache, but the cache is not populated yet, only one request to the server will be made and
9893 * the remaining requests will be fulfilled using the response from the first request.
9894 *
9895 * You can change the default cache to a new object (built with
9896 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
9897 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
9898 * their `cache` property to `true` will now use this cache object.
9899 *
9900 * If you set the default cache to `false` then only requests that specify their own custom
9901 * cache object will be cached.
9902 *
9903 * ## Interceptors
9904 *
9905 * Before you start creating interceptors, be sure to understand the
9906 * {@link ng.$q $q and deferred/promise APIs}.
9907 *
9908 * For purposes of global error handling, authentication, or any kind of synchronous or
9909 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
9910 * able to intercept requests before they are handed to the server and
9911 * responses before they are handed over to the application code that
9912 * initiated these requests. The interceptors leverage the {@link ng.$q
9913 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
9914 *
9915 * The interceptors are service factories that are registered with the `$httpProvider` by
9916 * adding them to the `$httpProvider.interceptors` array. The factory is called and
9917 * injected with dependencies (if specified) and returns the interceptor.
9918 *
9919 * There are two kinds of interceptors (and two kinds of rejection interceptors):
9920 *
9921 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
9922 * modify the `config` object or create a new one. The function needs to return the `config`
9923 * object directly, or a promise containing the `config` or a new `config` object.
9924 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
9925 * resolved with a rejection.
9926 * * `response`: interceptors get called with http `response` object. The function is free to
9927 * modify the `response` object or create a new one. The function needs to return the `response`
9928 * object directly, or as a promise containing the `response` or a new `response` object.
9929 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
9930 * resolved with a rejection.
9931 *
9932 *
9933 * ```js
9934 * // register the interceptor as a service
9935 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
9936 * return {
9937 * // optional method
9938 * 'request': function(config) {
9939 * // do something on success
9940 * return config;
9941 * },
9942 *
9943 * // optional method
9944 * 'requestError': function(rejection) {
9945 * // do something on error
9946 * if (canRecover(rejection)) {
9947 * return responseOrNewPromise
9948 * }
9949 * return $q.reject(rejection);
9950 * },
9951 *
9952 *
9953 *
9954 * // optional method
9955 * 'response': function(response) {
9956 * // do something on success
9957 * return response;
9958 * },
9959 *
9960 * // optional method
9961 * 'responseError': function(rejection) {
9962 * // do something on error
9963 * if (canRecover(rejection)) {
9964 * return responseOrNewPromise
9965 * }
9966 * return $q.reject(rejection);
9967 * }
9968 * };
9969 * });
9970 *
9971 * $httpProvider.interceptors.push('myHttpInterceptor');
9972 *
9973 *
9974 * // alternatively, register the interceptor via an anonymous factory
9975 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
9976 * return {
9977 * 'request': function(config) {
9978 * // same as above
9979 * },
9980 *
9981 * 'response': function(response) {
9982 * // same as above
9983 * }
9984 * };
9985 * });
9986 * ```
9987 *
9988 * ## Security Considerations
9989 *
9990 * When designing web applications, consider security threats from:
9991 *
9992 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
9993 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
9994 *
9995 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
9996 * pre-configured with strategies that address these issues, but for this to work backend server
9997 * cooperation is required.
9998 *
9999 * ### JSON Vulnerability Protection
10000 *
10001 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10002 * allows third party website to turn your JSON resource URL into
10003 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10004 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10005 * Angular will automatically strip the prefix before processing it as JSON.
10006 *
10007 * For example if your server needs to return:
10008 * ```js
10009 * ['one','two']
10010 * ```
10011 *
10012 * which is vulnerable to attack, your server can return:
10013 * ```js
10014 * )]}',
10015 * ['one','two']
10016 * ```
10017 *
10018 * Angular will strip the prefix, before processing the JSON.
10019 *
10020 *
10021 * ### Cross Site Request Forgery (XSRF) Protection
10022 *
10023 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10024 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10025 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10026 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10027 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10028 * the XHR came from JavaScript running on your domain. The header will not be set for
10029 * cross-domain requests.
10030 *
10031 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10032 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10033 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10034 * that only JavaScript running on your domain could have sent the request. The token must be
10035 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10036 * making up its own tokens). We recommend that the token is a digest of your site's
10037 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
10038 * for added security.
10039 *
10040 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10041 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10042 * or the per-request config object.
10043 *
10044 * In order to prevent collisions in environments where multiple Angular apps share the
10045 * same domain or subdomain, we recommend that each application uses unique cookie name.
10046 *
10047 * @param {object} config Object describing the request to be made and how it should be
10048 * processed. The object has following properties:
10049 *
10050 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10051 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10052 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10053 * with the `paramSerializer` and appended as GET parameters.
10054 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10055 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10056 * HTTP headers to send to the server. If the return value of a function is null, the
10057 * header will not be sent. Functions accept a config object as an argument.
10058 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10059 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10060 * - **transformRequest** –
10061 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10062 * transform function or an array of such functions. The transform function takes the http
10063 * request body and headers and returns its transformed (typically serialized) version.
10064 * See {@link ng.$http#overriding-the-default-transformations-per-request
10065 * Overriding the Default Transformations}
10066 * - **transformResponse** –
10067 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10068 * transform function or an array of such functions. The transform function takes the http
10069 * response body, headers and status and returns its transformed (typically deserialized) version.
10070 * See {@link ng.$http#overriding-the-default-transformations-per-request
10071 * Overriding the Default TransformationjqLiks}
10072 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10073 * prepare the string representation of request parameters (specified as an object).
10074 * If specified as string, it is interpreted as function registered with the
10075 * {@link $injector $injector}, which means you can create your own serializer
10076 * by registering it as a {@link auto.$provide#service service}.
10077 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10078 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10079 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10080 * GET request, otherwise if a cache instance built with
10081 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10082 * caching.
10083 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10084 * that should abort the request when resolved.
10085 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10086 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10087 * for more information.
10088 * - **responseType** - `{string}` - see
10089 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10090 *
10091 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10092 * when the request succeeds or fails.
10093 *
10094 *
10095 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10096 * requests. This is primarily meant to be used for debugging purposes.
10097 *
10098 *
10099 * @example
10100<example module="httpExample">
10101<file name="index.html">
10102 <div ng-controller="FetchController">
10103 <select ng-model="method" aria-label="Request method">
10104 <option>GET</option>
10105 <option>JSONP</option>
10106 </select>
10107 <input type="text" ng-model="url" size="80" aria-label="URL" />
10108 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10109 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10110 <button id="samplejsonpbtn"
10111 ng-click="updateModel('JSONP',
10112 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10113 Sample JSONP
10114 </button>
10115 <button id="invalidjsonpbtn"
10116 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10117 Invalid JSONP
10118 </button>
10119 <pre>http status code: {{status}}</pre>
10120 <pre>http response data: {{data}}</pre>
10121 </div>
10122</file>
10123<file name="script.js">
10124 angular.module('httpExample', [])
10125 .controller('FetchController', ['$scope', '$http', '$templateCache',
10126 function($scope, $http, $templateCache) {
10127 $scope.method = 'GET';
10128 $scope.url = 'http-hello.html';
10129
10130 $scope.fetch = function() {
10131 $scope.code = null;
10132 $scope.response = null;
10133
10134 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10135 then(function(response) {
10136 $scope.status = response.status;
10137 $scope.data = response.data;
10138 }, function(response) {
10139 $scope.data = response.data || "Request failed";
10140 $scope.status = response.status;
10141 });
10142 };
10143
10144 $scope.updateModel = function(method, url) {
10145 $scope.method = method;
10146 $scope.url = url;
10147 };
10148 }]);
10149</file>
10150<file name="http-hello.html">
10151 Hello, $http!
10152</file>
10153<file name="protractor.js" type="protractor">
10154 var status = element(by.binding('status'));
10155 var data = element(by.binding('data'));
10156 var fetchBtn = element(by.id('fetchbtn'));
10157 var sampleGetBtn = element(by.id('samplegetbtn'));
10158 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10159 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10160
10161 it('should make an xhr GET request', function() {
10162 sampleGetBtn.click();
10163 fetchBtn.click();
10164 expect(status.getText()).toMatch('200');
10165 expect(data.getText()).toMatch(/Hello, \$http!/);
10166 });
10167
10168// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10169// it('should make a JSONP request to angularjs.org', function() {
10170// sampleJsonpBtn.click();
10171// fetchBtn.click();
10172// expect(status.getText()).toMatch('200');
10173// expect(data.getText()).toMatch(/Super Hero!/);
10174// });
10175
10176 it('should make JSONP request to invalid URL and invoke the error handler',
10177 function() {
10178 invalidJsonpBtn.click();
10179 fetchBtn.click();
10180 expect(status.getText()).toMatch('0');
10181 expect(data.getText()).toMatch('Request failed');
10182 });
10183</file>
10184</example>
10185 */
10186 function $http(requestConfig) {
10187
10188 if (!angular.isObject(requestConfig)) {
10189 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10190 }
10191
10192 var config = extend({
10193 method: 'get',
10194 transformRequest: defaults.transformRequest,
10195 transformResponse: defaults.transformResponse,
10196 paramSerializer: defaults.paramSerializer
10197 }, requestConfig);
10198
10199 config.headers = mergeHeaders(requestConfig);
10200 config.method = uppercase(config.method);
10201 config.paramSerializer = isString(config.paramSerializer) ?
10202 $injector.get(config.paramSerializer) : config.paramSerializer;
10203
10204 var serverRequest = function(config) {
10205 var headers = config.headers;
10206 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10207
10208 // strip content-type if data is undefined
10209 if (isUndefined(reqData)) {
10210 forEach(headers, function(value, header) {
10211 if (lowercase(header) === 'content-type') {
10212 delete headers[header];
10213 }
10214 });
10215 }
10216
10217 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10218 config.withCredentials = defaults.withCredentials;
10219 }
10220
10221 // send request
10222 return sendReq(config, reqData).then(transformResponse, transformResponse);
10223 };
10224
10225 var chain = [serverRequest, undefined];
10226 var promise = $q.when(config);
10227
10228 // apply interceptors
10229 forEach(reversedInterceptors, function(interceptor) {
10230 if (interceptor.request || interceptor.requestError) {
10231 chain.unshift(interceptor.request, interceptor.requestError);
10232 }
10233 if (interceptor.response || interceptor.responseError) {
10234 chain.push(interceptor.response, interceptor.responseError);
10235 }
10236 });
10237
10238 while (chain.length) {
10239 var thenFn = chain.shift();
10240 var rejectFn = chain.shift();
10241
10242 promise = promise.then(thenFn, rejectFn);
10243 }
10244
10245 if (useLegacyPromise) {
10246 promise.success = function(fn) {
10247 assertArgFn(fn, 'fn');
10248
10249 promise.then(function(response) {
10250 fn(response.data, response.status, response.headers, config);
10251 });
10252 return promise;
10253 };
10254
10255 promise.error = function(fn) {
10256 assertArgFn(fn, 'fn');
10257
10258 promise.then(null, function(response) {
10259 fn(response.data, response.status, response.headers, config);
10260 });
10261 return promise;
10262 };
10263 } else {
10264 promise.success = $httpMinErrLegacyFn('success');
10265 promise.error = $httpMinErrLegacyFn('error');
10266 }
10267
10268 return promise;
10269
10270 function transformResponse(response) {
10271 // make a copy since the response must be cacheable
10272 var resp = extend({}, response);
10273 if (!response.data) {
10274 resp.data = response.data;
10275 } else {
10276 resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
10277 }
10278 return (isSuccess(response.status))
10279 ? resp
10280 : $q.reject(resp);
10281 }
10282
10283 function executeHeaderFns(headers, config) {
10284 var headerContent, processedHeaders = {};
10285
10286 forEach(headers, function(headerFn, header) {
10287 if (isFunction(headerFn)) {
10288 headerContent = headerFn(config);
10289 if (headerContent != null) {
10290 processedHeaders[header] = headerContent;
10291 }
10292 } else {
10293 processedHeaders[header] = headerFn;
10294 }
10295 });
10296
10297 return processedHeaders;
10298 }
10299
10300 function mergeHeaders(config) {
10301 var defHeaders = defaults.headers,
10302 reqHeaders = extend({}, config.headers),
10303 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10304
10305 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10306
10307 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10308 defaultHeadersIteration:
10309 for (defHeaderName in defHeaders) {
10310 lowercaseDefHeaderName = lowercase(defHeaderName);
10311
10312 for (reqHeaderName in reqHeaders) {
10313 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10314 continue defaultHeadersIteration;
10315 }
10316 }
10317
10318 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10319 }
10320
10321 // execute if header value is a function for merged headers
10322 return executeHeaderFns(reqHeaders, shallowCopy(config));
10323 }
10324 }
10325
10326 $http.pendingRequests = [];
10327
10328 /**
10329 * @ngdoc method
10330 * @name $http#get
10331 *
10332 * @description
10333 * Shortcut method to perform `GET` request.
10334 *
10335 * @param {string} url Relative or absolute URL specifying the destination of the request
10336 * @param {Object=} config Optional configuration object
10337 * @returns {HttpPromise} Future object
10338 */
10339
10340 /**
10341 * @ngdoc method
10342 * @name $http#delete
10343 *
10344 * @description
10345 * Shortcut method to perform `DELETE` request.
10346 *
10347 * @param {string} url Relative or absolute URL specifying the destination of the request
10348 * @param {Object=} config Optional configuration object
10349 * @returns {HttpPromise} Future object
10350 */
10351
10352 /**
10353 * @ngdoc method
10354 * @name $http#head
10355 *
10356 * @description
10357 * Shortcut method to perform `HEAD` request.
10358 *
10359 * @param {string} url Relative or absolute URL specifying the destination of the request
10360 * @param {Object=} config Optional configuration object
10361 * @returns {HttpPromise} Future object
10362 */
10363
10364 /**
10365 * @ngdoc method
10366 * @name $http#jsonp
10367 *
10368 * @description
10369 * Shortcut method to perform `JSONP` request.
10370 *
10371 * @param {string} url Relative or absolute URL specifying the destination of the request.
10372 * The name of the callback should be the string `JSON_CALLBACK`.
10373 * @param {Object=} config Optional configuration object
10374 * @returns {HttpPromise} Future object
10375 */
10376 createShortMethods('get', 'delete', 'head', 'jsonp');
10377
10378 /**
10379 * @ngdoc method
10380 * @name $http#post
10381 *
10382 * @description
10383 * Shortcut method to perform `POST` request.
10384 *
10385 * @param {string} url Relative or absolute URL specifying the destination of the request
10386 * @param {*} data Request content
10387 * @param {Object=} config Optional configuration object
10388 * @returns {HttpPromise} Future object
10389 */
10390
10391 /**
10392 * @ngdoc method
10393 * @name $http#put
10394 *
10395 * @description
10396 * Shortcut method to perform `PUT` request.
10397 *
10398 * @param {string} url Relative or absolute URL specifying the destination of the request
10399 * @param {*} data Request content
10400 * @param {Object=} config Optional configuration object
10401 * @returns {HttpPromise} Future object
10402 */
10403
10404 /**
10405 * @ngdoc method
10406 * @name $http#patch
10407 *
10408 * @description
10409 * Shortcut method to perform `PATCH` request.
10410 *
10411 * @param {string} url Relative or absolute URL specifying the destination of the request
10412 * @param {*} data Request content
10413 * @param {Object=} config Optional configuration object
10414 * @returns {HttpPromise} Future object
10415 */
10416 createShortMethodsWithData('post', 'put', 'patch');
10417
10418 /**
10419 * @ngdoc property
10420 * @name $http#defaults
10421 *
10422 * @description
10423 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10424 * default headers, withCredentials as well as request and response transformations.
10425 *
10426 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10427 */
10428 $http.defaults = defaults;
10429
10430
10431 return $http;
10432
10433
10434 function createShortMethods(names) {
10435 forEach(arguments, function(name) {
10436 $http[name] = function(url, config) {
10437 return $http(extend({}, config || {}, {
10438 method: name,
10439 url: url
10440 }));
10441 };
10442 });
10443 }
10444
10445
10446 function createShortMethodsWithData(name) {
10447 forEach(arguments, function(name) {
10448 $http[name] = function(url, data, config) {
10449 return $http(extend({}, config || {}, {
10450 method: name,
10451 url: url,
10452 data: data
10453 }));
10454 };
10455 });
10456 }
10457
10458
10459 /**
10460 * Makes the request.
10461 *
10462 * !!! ACCESSES CLOSURE VARS:
10463 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10464 */
10465 function sendReq(config, reqData) {
10466 var deferred = $q.defer(),
10467 promise = deferred.promise,
10468 cache,
10469 cachedResp,
10470 reqHeaders = config.headers,
10471 url = buildUrl(config.url, config.paramSerializer(config.params));
10472
10473 $http.pendingRequests.push(config);
10474 promise.then(removePendingReq, removePendingReq);
10475
10476
10477 if ((config.cache || defaults.cache) && config.cache !== false &&
10478 (config.method === 'GET' || config.method === 'JSONP')) {
10479 cache = isObject(config.cache) ? config.cache
10480 : isObject(defaults.cache) ? defaults.cache
10481 : defaultCache;
10482 }
10483
10484 if (cache) {
10485 cachedResp = cache.get(url);
10486 if (isDefined(cachedResp)) {
10487 if (isPromiseLike(cachedResp)) {
10488 // cached request has already been sent, but there is no response yet
10489 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10490 } else {
10491 // serving from cache
10492 if (isArray(cachedResp)) {
10493 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10494 } else {
10495 resolvePromise(cachedResp, 200, {}, 'OK');
10496 }
10497 }
10498 } else {
10499 // put the promise for the non-transformed response into cache as a placeholder
10500 cache.put(url, promise);
10501 }
10502 }
10503
10504
10505 // if we won't have the response in cache, set the xsrf headers and
10506 // send the request to the backend
10507 if (isUndefined(cachedResp)) {
10508 var xsrfValue = urlIsSameOrigin(config.url)
10509 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10510 : undefined;
10511 if (xsrfValue) {
10512 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10513 }
10514
10515 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10516 config.withCredentials, config.responseType);
10517 }
10518
10519 return promise;
10520
10521
10522 /**
10523 * Callback registered to $httpBackend():
10524 * - caches the response if desired
10525 * - resolves the raw $http promise
10526 * - calls $apply
10527 */
10528 function done(status, response, headersString, statusText) {
10529 if (cache) {
10530 if (isSuccess(status)) {
10531 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10532 } else {
10533 // remove promise from the cache
10534 cache.remove(url);
10535 }
10536 }
10537
10538 function resolveHttpPromise() {
10539 resolvePromise(response, status, headersString, statusText);
10540 }
10541
10542 if (useApplyAsync) {
10543 $rootScope.$applyAsync(resolveHttpPromise);
10544 } else {
10545 resolveHttpPromise();
10546 if (!$rootScope.$$phase) $rootScope.$apply();
10547 }
10548 }
10549
10550
10551 /**
10552 * Resolves the raw $http promise.
10553 */
10554 function resolvePromise(response, status, headers, statusText) {
10555 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10556 status = status >= -1 ? status : 0;
10557
10558 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10559 data: response,
10560 status: status,
10561 headers: headersGetter(headers),
10562 config: config,
10563 statusText: statusText
10564 });
10565 }
10566
10567 function resolvePromiseWithResult(result) {
10568 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10569 }
10570
10571 function removePendingReq() {
10572 var idx = $http.pendingRequests.indexOf(config);
10573 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10574 }
10575 }
10576
10577
10578 function buildUrl(url, serializedParams) {
10579 if (serializedParams.length > 0) {
10580 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10581 }
10582 return url;
10583 }
10584 }];
10585}
10586
10587/**
10588 * @ngdoc service
10589 * @name $xhrFactory
10590 *
10591 * @description
10592 * Factory function used to create XMLHttpRequest objects.
10593 *
10594 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10595 *
10596 * ```
10597 * angular.module('myApp', [])
10598 * .factory('$xhrFactory', function() {
10599 * return function createXhr(method, url) {
10600 * return new window.XMLHttpRequest({mozSystem: true});
10601 * };
10602 * });
10603 * ```
10604 *
10605 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10606 * @param {string} url URL of the request.
10607 */
10608function $xhrFactoryProvider() {
10609 this.$get = function() {
10610 return function createXhr() {
10611 return new window.XMLHttpRequest();
10612 };
10613 };
10614}
10615
10616/**
10617 * @ngdoc service
10618 * @name $httpBackend
10619 * @requires $window
10620 * @requires $document
10621 * @requires $xhrFactory
10622 *
10623 * @description
10624 * HTTP backend used by the {@link ng.$http service} that delegates to
10625 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10626 *
10627 * You should never need to use this service directly, instead use the higher-level abstractions:
10628 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10629 *
10630 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10631 * $httpBackend} which can be trained with responses.
10632 */
10633function $HttpBackendProvider() {
10634 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10635 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10636 }];
10637}
10638
10639function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10640 // TODO(vojta): fix the signature
10641 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10642 $browser.$$incOutstandingRequestCount();
10643 url = url || $browser.url();
10644
10645 if (lowercase(method) == 'jsonp') {
10646 var callbackId = '_' + (callbacks.counter++).toString(36);
10647 callbacks[callbackId] = function(data) {
10648 callbacks[callbackId].data = data;
10649 callbacks[callbackId].called = true;
10650 };
10651
10652 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10653 callbackId, function(status, text) {
10654 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10655 callbacks[callbackId] = noop;
10656 });
10657 } else {
10658
10659 var xhr = createXhr(method, url);
10660
10661 xhr.open(method, url, true);
10662 forEach(headers, function(value, key) {
10663 if (isDefined(value)) {
10664 xhr.setRequestHeader(key, value);
10665 }
10666 });
10667
10668 xhr.onload = function requestLoaded() {
10669 var statusText = xhr.statusText || '';
10670
10671 // responseText is the old-school way of retrieving response (supported by IE9)
10672 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10673 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10674
10675 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10676 var status = xhr.status === 1223 ? 204 : xhr.status;
10677
10678 // fix status code when it is 0 (0 status is undocumented).
10679 // Occurs when accessing file resources or on Android 4.1 stock browser
10680 // while retrieving files from application cache.
10681 if (status === 0) {
10682 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10683 }
10684
10685 completeRequest(callback,
10686 status,
10687 response,
10688 xhr.getAllResponseHeaders(),
10689 statusText);
10690 };
10691
10692 var requestError = function() {
10693 // The response is always empty
10694 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10695 completeRequest(callback, -1, null, null, '');
10696 };
10697
10698 xhr.onerror = requestError;
10699 xhr.onabort = requestError;
10700
10701 if (withCredentials) {
10702 xhr.withCredentials = true;
10703 }
10704
10705 if (responseType) {
10706 try {
10707 xhr.responseType = responseType;
10708 } catch (e) {
10709 // WebKit added support for the json responseType value on 09/03/2013
10710 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10711 // known to throw when setting the value "json" as the response type. Other older
10712 // browsers implementing the responseType
10713 //
10714 // The json response type can be ignored if not supported, because JSON payloads are
10715 // parsed on the client-side regardless.
10716 if (responseType !== 'json') {
10717 throw e;
10718 }
10719 }
10720 }
10721
10722 xhr.send(isUndefined(post) ? null : post);
10723 }
10724
10725 if (timeout > 0) {
10726 var timeoutId = $browserDefer(timeoutRequest, timeout);
10727 } else if (isPromiseLike(timeout)) {
10728 timeout.then(timeoutRequest);
10729 }
10730
10731
10732 function timeoutRequest() {
10733 jsonpDone && jsonpDone();
10734 xhr && xhr.abort();
10735 }
10736
10737 function completeRequest(callback, status, response, headersString, statusText) {
10738 // cancel timeout and subsequent timeout promise resolution
10739 if (isDefined(timeoutId)) {
10740 $browserDefer.cancel(timeoutId);
10741 }
10742 jsonpDone = xhr = null;
10743
10744 callback(status, response, headersString, statusText);
10745 $browser.$$completeOutstandingRequest(noop);
10746 }
10747 };
10748
10749 function jsonpReq(url, callbackId, done) {
10750 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10751 // - fetches local scripts via XHR and evals them
10752 // - adds and immediately removes script elements from the document
10753 var script = rawDocument.createElement('script'), callback = null;
10754 script.type = "text/javascript";
10755 script.src = url;
10756 script.async = true;
10757
10758 callback = function(event) {
10759 removeEventListenerFn(script, "load", callback);
10760 removeEventListenerFn(script, "error", callback);
10761 rawDocument.body.removeChild(script);
10762 script = null;
10763 var status = -1;
10764 var text = "unknown";
10765
10766 if (event) {
10767 if (event.type === "load" && !callbacks[callbackId].called) {
10768 event = { type: "error" };
10769 }
10770 text = event.type;
10771 status = event.type === "error" ? 404 : 200;
10772 }
10773
10774 if (done) {
10775 done(status, text);
10776 }
10777 };
10778
10779 addEventListenerFn(script, "load", callback);
10780 addEventListenerFn(script, "error", callback);
10781 rawDocument.body.appendChild(script);
10782 return callback;
10783 }
10784}
10785
10786var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10787$interpolateMinErr.throwNoconcat = function(text) {
10788 throw $interpolateMinErr('noconcat',
10789 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10790 "interpolations that concatenate multiple expressions when a trusted value is " +
10791 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10792};
10793
10794$interpolateMinErr.interr = function(text, err) {
10795 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10796};
10797
10798/**
10799 * @ngdoc provider
10800 * @name $interpolateProvider
10801 *
10802 * @description
10803 *
10804 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10805 *
10806 * @example
10807<example module="customInterpolationApp">
10808<file name="index.html">
10809<script>
10810 var customInterpolationApp = angular.module('customInterpolationApp', []);
10811
10812 customInterpolationApp.config(function($interpolateProvider) {
10813 $interpolateProvider.startSymbol('//');
10814 $interpolateProvider.endSymbol('//');
10815 });
10816
10817
10818 customInterpolationApp.controller('DemoController', function() {
10819 this.label = "This binding is brought you by // interpolation symbols.";
10820 });
10821</script>
10822<div ng-app="App" ng-controller="DemoController as demo">
10823 //demo.label//
10824</div>
10825</file>
10826<file name="protractor.js" type="protractor">
10827 it('should interpolate binding with custom symbols', function() {
10828 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10829 });
10830</file>
10831</example>
10832 */
10833function $InterpolateProvider() {
10834 var startSymbol = '{{';
10835 var endSymbol = '}}';
10836
10837 /**
10838 * @ngdoc method
10839 * @name $interpolateProvider#startSymbol
10840 * @description
10841 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10842 *
10843 * @param {string=} value new value to set the starting symbol to.
10844 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10845 */
10846 this.startSymbol = function(value) {
10847 if (value) {
10848 startSymbol = value;
10849 return this;
10850 } else {
10851 return startSymbol;
10852 }
10853 };
10854
10855 /**
10856 * @ngdoc method
10857 * @name $interpolateProvider#endSymbol
10858 * @description
10859 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10860 *
10861 * @param {string=} value new value to set the ending symbol to.
10862 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10863 */
10864 this.endSymbol = function(value) {
10865 if (value) {
10866 endSymbol = value;
10867 return this;
10868 } else {
10869 return endSymbol;
10870 }
10871 };
10872
10873
10874 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10875 var startSymbolLength = startSymbol.length,
10876 endSymbolLength = endSymbol.length,
10877 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
10878 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
10879
10880 function escape(ch) {
10881 return '\\\\\\' + ch;
10882 }
10883
10884 function unescapeText(text) {
10885 return text.replace(escapedStartRegexp, startSymbol).
10886 replace(escapedEndRegexp, endSymbol);
10887 }
10888
10889 function stringify(value) {
10890 if (value == null) { // null || undefined
10891 return '';
10892 }
10893 switch (typeof value) {
10894 case 'string':
10895 break;
10896 case 'number':
10897 value = '' + value;
10898 break;
10899 default:
10900 value = toJson(value);
10901 }
10902
10903 return value;
10904 }
10905
10906 /**
10907 * @ngdoc service
10908 * @name $interpolate
10909 * @kind function
10910 *
10911 * @requires $parse
10912 * @requires $sce
10913 *
10914 * @description
10915 *
10916 * Compiles a string with markup into an interpolation function. This service is used by the
10917 * HTML {@link ng.$compile $compile} service for data binding. See
10918 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
10919 * interpolation markup.
10920 *
10921 *
10922 * ```js
10923 * var $interpolate = ...; // injected
10924 * var exp = $interpolate('Hello {{name | uppercase}}!');
10925 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
10926 * ```
10927 *
10928 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
10929 * `true`, the interpolation function will return `undefined` unless all embedded expressions
10930 * evaluate to a value other than `undefined`.
10931 *
10932 * ```js
10933 * var $interpolate = ...; // injected
10934 * var context = {greeting: 'Hello', name: undefined };
10935 *
10936 * // default "forgiving" mode
10937 * var exp = $interpolate('{{greeting}} {{name}}!');
10938 * expect(exp(context)).toEqual('Hello !');
10939 *
10940 * // "allOrNothing" mode
10941 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
10942 * expect(exp(context)).toBeUndefined();
10943 * context.name = 'Angular';
10944 * expect(exp(context)).toEqual('Hello Angular!');
10945 * ```
10946 *
10947 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
10948 *
10949 * ####Escaped Interpolation
10950 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
10951 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
10952 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
10953 * or binding.
10954 *
10955 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
10956 * degree, while also enabling code examples to work without relying on the
10957 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
10958 *
10959 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
10960 * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
10961 * interpolation start/end markers with their escaped counterparts.**
10962 *
10963 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
10964 * output when the $interpolate service processes the text. So, for HTML elements interpolated
10965 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
10966 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
10967 * this is typically useful only when user-data is used in rendering a template from the server, or
10968 * when otherwise untrusted data is used by a directive.
10969 *
10970 * <example>
10971 * <file name="index.html">
10972 * <div ng-init="username='A user'">
10973 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
10974 * </p>
10975 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
10976 * application, but fails to accomplish their task, because the server has correctly
10977 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
10978 * characters.</p>
10979 * <p>Instead, the result of the attempted script injection is visible, and can be removed
10980 * from the database by an administrator.</p>
10981 * </div>
10982 * </file>
10983 * </example>
10984 *
10985 * @param {string} text The text with markup to interpolate.
10986 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
10987 * embedded expression in order to return an interpolation function. Strings with no
10988 * embedded expression will return null for the interpolation function.
10989 * @param {string=} trustedContext when provided, the returned function passes the interpolated
10990 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
10991 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
10992 * provides Strict Contextual Escaping for details.
10993 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
10994 * unless all embedded expressions evaluate to a value other than `undefined`.
10995 * @returns {function(context)} an interpolation function which is used to compute the
10996 * interpolated string. The function has these parameters:
10997 *
10998 * - `context`: evaluation context for all expressions embedded in the interpolated text
10999 */
11000 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11001 allOrNothing = !!allOrNothing;
11002 var startIndex,
11003 endIndex,
11004 index = 0,
11005 expressions = [],
11006 parseFns = [],
11007 textLength = text.length,
11008 exp,
11009 concat = [],
11010 expressionPositions = [];
11011
11012 while (index < textLength) {
11013 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11014 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11015 if (index !== startIndex) {
11016 concat.push(unescapeText(text.substring(index, startIndex)));
11017 }
11018 exp = text.substring(startIndex + startSymbolLength, endIndex);
11019 expressions.push(exp);
11020 parseFns.push($parse(exp, parseStringifyInterceptor));
11021 index = endIndex + endSymbolLength;
11022 expressionPositions.push(concat.length);
11023 concat.push('');
11024 } else {
11025 // we did not find an interpolation, so we have to add the remainder to the separators array
11026 if (index !== textLength) {
11027 concat.push(unescapeText(text.substring(index)));
11028 }
11029 break;
11030 }
11031 }
11032
11033 // Concatenating expressions makes it hard to reason about whether some combination of
11034 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11035 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11036 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11037 // make it obvious that you bound the value to some user controlled value. This helps reduce
11038 // the load when auditing for XSS issues.
11039 if (trustedContext && concat.length > 1) {
11040 $interpolateMinErr.throwNoconcat(text);
11041 }
11042
11043 if (!mustHaveExpression || expressions.length) {
11044 var compute = function(values) {
11045 for (var i = 0, ii = expressions.length; i < ii; i++) {
11046 if (allOrNothing && isUndefined(values[i])) return;
11047 concat[expressionPositions[i]] = values[i];
11048 }
11049 return concat.join('');
11050 };
11051
11052 var getValue = function(value) {
11053 return trustedContext ?
11054 $sce.getTrusted(trustedContext, value) :
11055 $sce.valueOf(value);
11056 };
11057
11058 return extend(function interpolationFn(context) {
11059 var i = 0;
11060 var ii = expressions.length;
11061 var values = new Array(ii);
11062
11063 try {
11064 for (; i < ii; i++) {
11065 values[i] = parseFns[i](context);
11066 }
11067
11068 return compute(values);
11069 } catch (err) {
11070 $exceptionHandler($interpolateMinErr.interr(text, err));
11071 }
11072
11073 }, {
11074 // all of these properties are undocumented for now
11075 exp: text, //just for compatibility with regular watchers created via $watch
11076 expressions: expressions,
11077 $$watchDelegate: function(scope, listener) {
11078 var lastValue;
11079 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11080 var currValue = compute(values);
11081 if (isFunction(listener)) {
11082 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11083 }
11084 lastValue = currValue;
11085 });
11086 }
11087 });
11088 }
11089
11090 function parseStringifyInterceptor(value) {
11091 try {
11092 value = getValue(value);
11093 return allOrNothing && !isDefined(value) ? value : stringify(value);
11094 } catch (err) {
11095 $exceptionHandler($interpolateMinErr.interr(text, err));
11096 }
11097 }
11098 }
11099
11100
11101 /**
11102 * @ngdoc method
11103 * @name $interpolate#startSymbol
11104 * @description
11105 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11106 *
11107 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11108 * the symbol.
11109 *
11110 * @returns {string} start symbol.
11111 */
11112 $interpolate.startSymbol = function() {
11113 return startSymbol;
11114 };
11115
11116
11117 /**
11118 * @ngdoc method
11119 * @name $interpolate#endSymbol
11120 * @description
11121 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11122 *
11123 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11124 * the symbol.
11125 *
11126 * @returns {string} end symbol.
11127 */
11128 $interpolate.endSymbol = function() {
11129 return endSymbol;
11130 };
11131
11132 return $interpolate;
11133 }];
11134}
11135
11136function $IntervalProvider() {
11137 this.$get = ['$rootScope', '$window', '$q', '$$q',
11138 function($rootScope, $window, $q, $$q) {
11139 var intervals = {};
11140
11141
11142 /**
11143 * @ngdoc service
11144 * @name $interval
11145 *
11146 * @description
11147 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11148 * milliseconds.
11149 *
11150 * The return value of registering an interval function is a promise. This promise will be
11151 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11152 * run indefinitely if `count` is not defined. The value of the notification will be the
11153 * number of iterations that have run.
11154 * To cancel an interval, call `$interval.cancel(promise)`.
11155 *
11156 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11157 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11158 * time.
11159 *
11160 * <div class="alert alert-warning">
11161 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11162 * with them. In particular they are not automatically destroyed when a controller's scope or a
11163 * directive's element are destroyed.
11164 * You should take this into consideration and make sure to always cancel the interval at the
11165 * appropriate moment. See the example below for more details on how and when to do this.
11166 * </div>
11167 *
11168 * @param {function()} fn A function that should be called repeatedly.
11169 * @param {number} delay Number of milliseconds between each function call.
11170 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11171 * indefinitely.
11172 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11173 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11174 * @param {...*=} Pass additional parameters to the executed function.
11175 * @returns {promise} A promise which will be notified on each iteration.
11176 *
11177 * @example
11178 * <example module="intervalExample">
11179 * <file name="index.html">
11180 * <script>
11181 * angular.module('intervalExample', [])
11182 * .controller('ExampleController', ['$scope', '$interval',
11183 * function($scope, $interval) {
11184 * $scope.format = 'M/d/yy h:mm:ss a';
11185 * $scope.blood_1 = 100;
11186 * $scope.blood_2 = 120;
11187 *
11188 * var stop;
11189 * $scope.fight = function() {
11190 * // Don't start a new fight if we are already fighting
11191 * if ( angular.isDefined(stop) ) return;
11192 *
11193 * stop = $interval(function() {
11194 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11195 * $scope.blood_1 = $scope.blood_1 - 3;
11196 * $scope.blood_2 = $scope.blood_2 - 4;
11197 * } else {
11198 * $scope.stopFight();
11199 * }
11200 * }, 100);
11201 * };
11202 *
11203 * $scope.stopFight = function() {
11204 * if (angular.isDefined(stop)) {
11205 * $interval.cancel(stop);
11206 * stop = undefined;
11207 * }
11208 * };
11209 *
11210 * $scope.resetFight = function() {
11211 * $scope.blood_1 = 100;
11212 * $scope.blood_2 = 120;
11213 * };
11214 *
11215 * $scope.$on('$destroy', function() {
11216 * // Make sure that the interval is destroyed too
11217 * $scope.stopFight();
11218 * });
11219 * }])
11220 * // Register the 'myCurrentTime' directive factory method.
11221 * // We inject $interval and dateFilter service since the factory method is DI.
11222 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11223 * function($interval, dateFilter) {
11224 * // return the directive link function. (compile function not needed)
11225 * return function(scope, element, attrs) {
11226 * var format, // date format
11227 * stopTime; // so that we can cancel the time updates
11228 *
11229 * // used to update the UI
11230 * function updateTime() {
11231 * element.text(dateFilter(new Date(), format));
11232 * }
11233 *
11234 * // watch the expression, and update the UI on change.
11235 * scope.$watch(attrs.myCurrentTime, function(value) {
11236 * format = value;
11237 * updateTime();
11238 * });
11239 *
11240 * stopTime = $interval(updateTime, 1000);
11241 *
11242 * // listen on DOM destroy (removal) event, and cancel the next UI update
11243 * // to prevent updating time after the DOM element was removed.
11244 * element.on('$destroy', function() {
11245 * $interval.cancel(stopTime);
11246 * });
11247 * }
11248 * }]);
11249 * </script>
11250 *
11251 * <div>
11252 * <div ng-controller="ExampleController">
11253 * <label>Date format: <input ng-model="format"></label> <hr/>
11254 * Current time is: <span my-current-time="format"></span>
11255 * <hr/>
11256 * Blood 1 : <font color='red'>{{blood_1}}</font>
11257 * Blood 2 : <font color='red'>{{blood_2}}</font>
11258 * <button type="button" data-ng-click="fight()">Fight</button>
11259 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11260 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11261 * </div>
11262 * </div>
11263 *
11264 * </file>
11265 * </example>
11266 */
11267 function interval(fn, delay, count, invokeApply) {
11268 var hasParams = arguments.length > 4,
11269 args = hasParams ? sliceArgs(arguments, 4) : [],
11270 setInterval = $window.setInterval,
11271 clearInterval = $window.clearInterval,
11272 iteration = 0,
11273 skipApply = (isDefined(invokeApply) && !invokeApply),
11274 deferred = (skipApply ? $$q : $q).defer(),
11275 promise = deferred.promise;
11276
11277 count = isDefined(count) ? count : 0;
11278
11279 promise.then(null, null, (!hasParams) ? fn : function() {
11280 fn.apply(null, args);
11281 });
11282
11283 promise.$$intervalId = setInterval(function tick() {
11284 deferred.notify(iteration++);
11285
11286 if (count > 0 && iteration >= count) {
11287 deferred.resolve(iteration);
11288 clearInterval(promise.$$intervalId);
11289 delete intervals[promise.$$intervalId];
11290 }
11291
11292 if (!skipApply) $rootScope.$apply();
11293
11294 }, delay);
11295
11296 intervals[promise.$$intervalId] = deferred;
11297
11298 return promise;
11299 }
11300
11301
11302 /**
11303 * @ngdoc method
11304 * @name $interval#cancel
11305 *
11306 * @description
11307 * Cancels a task associated with the `promise`.
11308 *
11309 * @param {Promise=} promise returned by the `$interval` function.
11310 * @returns {boolean} Returns `true` if the task was successfully canceled.
11311 */
11312 interval.cancel = function(promise) {
11313 if (promise && promise.$$intervalId in intervals) {
11314 intervals[promise.$$intervalId].reject('canceled');
11315 $window.clearInterval(promise.$$intervalId);
11316 delete intervals[promise.$$intervalId];
11317 return true;
11318 }
11319 return false;
11320 };
11321
11322 return interval;
11323 }];
11324}
11325
11326/**
11327 * @ngdoc service
11328 * @name $locale
11329 *
11330 * @description
11331 * $locale service provides localization rules for various Angular components. As of right now the
11332 * only public api is:
11333 *
11334 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11335 */
11336
11337var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11338 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11339var $locationMinErr = minErr('$location');
11340
11341
11342/**
11343 * Encode path using encodeUriSegment, ignoring forward slashes
11344 *
11345 * @param {string} path Path to encode
11346 * @returns {string}
11347 */
11348function encodePath(path) {
11349 var segments = path.split('/'),
11350 i = segments.length;
11351
11352 while (i--) {
11353 segments[i] = encodeUriSegment(segments[i]);
11354 }
11355
11356 return segments.join('/');
11357}
11358
11359function parseAbsoluteUrl(absoluteUrl, locationObj) {
11360 var parsedUrl = urlResolve(absoluteUrl);
11361
11362 locationObj.$$protocol = parsedUrl.protocol;
11363 locationObj.$$host = parsedUrl.hostname;
11364 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11365}
11366
11367
11368function parseAppUrl(relativeUrl, locationObj) {
11369 var prefixed = (relativeUrl.charAt(0) !== '/');
11370 if (prefixed) {
11371 relativeUrl = '/' + relativeUrl;
11372 }
11373 var match = urlResolve(relativeUrl);
11374 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11375 match.pathname.substring(1) : match.pathname);
11376 locationObj.$$search = parseKeyValue(match.search);
11377 locationObj.$$hash = decodeURIComponent(match.hash);
11378
11379 // make sure path starts with '/';
11380 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11381 locationObj.$$path = '/' + locationObj.$$path;
11382 }
11383}
11384
11385
11386/**
11387 *
11388 * @param {string} begin
11389 * @param {string} whole
11390 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11391 * expected string.
11392 */
11393function beginsWith(begin, whole) {
11394 if (whole.indexOf(begin) === 0) {
11395 return whole.substr(begin.length);
11396 }
11397}
11398
11399
11400function stripHash(url) {
11401 var index = url.indexOf('#');
11402 return index == -1 ? url : url.substr(0, index);
11403}
11404
11405function trimEmptyHash(url) {
11406 return url.replace(/(#.+)|#$/, '$1');
11407}
11408
11409
11410function stripFile(url) {
11411 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11412}
11413
11414/* return the server only (scheme://host:port) */
11415function serverBase(url) {
11416 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11417}
11418
11419
11420/**
11421 * LocationHtml5Url represents an url
11422 * This object is exposed as $location service when HTML5 mode is enabled and supported
11423 *
11424 * @constructor
11425 * @param {string} appBase application base URL
11426 * @param {string} appBaseNoFile application base URL stripped of any filename
11427 * @param {string} basePrefix url path prefix
11428 */
11429function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11430 this.$$html5 = true;
11431 basePrefix = basePrefix || '';
11432 parseAbsoluteUrl(appBase, this);
11433
11434
11435 /**
11436 * Parse given html5 (regular) url string into properties
11437 * @param {string} url HTML5 url
11438 * @private
11439 */
11440 this.$$parse = function(url) {
11441 var pathUrl = beginsWith(appBaseNoFile, url);
11442 if (!isString(pathUrl)) {
11443 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11444 appBaseNoFile);
11445 }
11446
11447 parseAppUrl(pathUrl, this);
11448
11449 if (!this.$$path) {
11450 this.$$path = '/';
11451 }
11452
11453 this.$$compose();
11454 };
11455
11456 /**
11457 * Compose url and update `absUrl` property
11458 * @private
11459 */
11460 this.$$compose = function() {
11461 var search = toKeyValue(this.$$search),
11462 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11463
11464 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11465 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11466 };
11467
11468 this.$$parseLinkUrl = function(url, relHref) {
11469 if (relHref && relHref[0] === '#') {
11470 // special case for links to hash fragments:
11471 // keep the old url and only replace the hash fragment
11472 this.hash(relHref.slice(1));
11473 return true;
11474 }
11475 var appUrl, prevAppUrl;
11476 var rewrittenUrl;
11477
11478 if (isDefined(appUrl = beginsWith(appBase, url))) {
11479 prevAppUrl = appUrl;
11480 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11481 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11482 } else {
11483 rewrittenUrl = appBase + prevAppUrl;
11484 }
11485 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11486 rewrittenUrl = appBaseNoFile + appUrl;
11487 } else if (appBaseNoFile == url + '/') {
11488 rewrittenUrl = appBaseNoFile;
11489 }
11490 if (rewrittenUrl) {
11491 this.$$parse(rewrittenUrl);
11492 }
11493 return !!rewrittenUrl;
11494 };
11495}
11496
11497
11498/**
11499 * LocationHashbangUrl represents url
11500 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11501 * It also serves as the base class for html5 mode fallback on legacy browsers.
11502 *
11503 * @constructor
11504 * @param {string} appBase application base URL
11505 * @param {string} appBaseNoFile application base URL stripped of any filename
11506 * @param {string} hashPrefix hashbang prefix
11507 */
11508function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11509
11510 parseAbsoluteUrl(appBase, this);
11511
11512
11513 /**
11514 * Parse given hashbang url into properties
11515 * @param {string} url Hashbang url
11516 * @private
11517 */
11518 this.$$parse = function(url) {
11519 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11520 var withoutHashUrl;
11521
11522 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11523
11524 // The rest of the url starts with a hash so we have
11525 // got either a hashbang path or a plain hash fragment
11526 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11527 if (isUndefined(withoutHashUrl)) {
11528 // There was no hashbang prefix so we just have a hash fragment
11529 withoutHashUrl = withoutBaseUrl;
11530 }
11531
11532 } else {
11533 // There was no hashbang path nor hash fragment:
11534 // If we are in HTML5 mode we use what is left as the path;
11535 // Otherwise we ignore what is left
11536 if (this.$$html5) {
11537 withoutHashUrl = withoutBaseUrl;
11538 } else {
11539 withoutHashUrl = '';
11540 if (isUndefined(withoutBaseUrl)) {
11541 appBase = url;
11542 this.replace();
11543 }
11544 }
11545 }
11546
11547 parseAppUrl(withoutHashUrl, this);
11548
11549 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11550
11551 this.$$compose();
11552
11553 /*
11554 * In Windows, on an anchor node on documents loaded from
11555 * the filesystem, the browser will return a pathname
11556 * prefixed with the drive name ('/C:/path') when a
11557 * pathname without a drive is set:
11558 * * a.setAttribute('href', '/foo')
11559 * * a.pathname === '/C:/foo' //true
11560 *
11561 * Inside of Angular, we're always using pathnames that
11562 * do not include drive names for routing.
11563 */
11564 function removeWindowsDriveName(path, url, base) {
11565 /*
11566 Matches paths for file protocol on windows,
11567 such as /C:/foo/bar, and captures only /foo/bar.
11568 */
11569 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11570
11571 var firstPathSegmentMatch;
11572
11573 //Get the relative path from the input URL.
11574 if (url.indexOf(base) === 0) {
11575 url = url.replace(base, '');
11576 }
11577
11578 // The input URL intentionally contains a first path segment that ends with a colon.
11579 if (windowsFilePathExp.exec(url)) {
11580 return path;
11581 }
11582
11583 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11584 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11585 }
11586 };
11587
11588 /**
11589 * Compose hashbang url and update `absUrl` property
11590 * @private
11591 */
11592 this.$$compose = function() {
11593 var search = toKeyValue(this.$$search),
11594 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11595
11596 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11597 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11598 };
11599
11600 this.$$parseLinkUrl = function(url, relHref) {
11601 if (stripHash(appBase) == stripHash(url)) {
11602 this.$$parse(url);
11603 return true;
11604 }
11605 return false;
11606 };
11607}
11608
11609
11610/**
11611 * LocationHashbangUrl represents url
11612 * This object is exposed as $location service when html5 history api is enabled but the browser
11613 * does not support it.
11614 *
11615 * @constructor
11616 * @param {string} appBase application base URL
11617 * @param {string} appBaseNoFile application base URL stripped of any filename
11618 * @param {string} hashPrefix hashbang prefix
11619 */
11620function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11621 this.$$html5 = true;
11622 LocationHashbangUrl.apply(this, arguments);
11623
11624 this.$$parseLinkUrl = function(url, relHref) {
11625 if (relHref && relHref[0] === '#') {
11626 // special case for links to hash fragments:
11627 // keep the old url and only replace the hash fragment
11628 this.hash(relHref.slice(1));
11629 return true;
11630 }
11631
11632 var rewrittenUrl;
11633 var appUrl;
11634
11635 if (appBase == stripHash(url)) {
11636 rewrittenUrl = url;
11637 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11638 rewrittenUrl = appBase + hashPrefix + appUrl;
11639 } else if (appBaseNoFile === url + '/') {
11640 rewrittenUrl = appBaseNoFile;
11641 }
11642 if (rewrittenUrl) {
11643 this.$$parse(rewrittenUrl);
11644 }
11645 return !!rewrittenUrl;
11646 };
11647
11648 this.$$compose = function() {
11649 var search = toKeyValue(this.$$search),
11650 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11651
11652 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11653 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11654 this.$$absUrl = appBase + hashPrefix + this.$$url;
11655 };
11656
11657}
11658
11659
11660var locationPrototype = {
11661
11662 /**
11663 * Are we in html5 mode?
11664 * @private
11665 */
11666 $$html5: false,
11667
11668 /**
11669 * Has any change been replacing?
11670 * @private
11671 */
11672 $$replace: false,
11673
11674 /**
11675 * @ngdoc method
11676 * @name $location#absUrl
11677 *
11678 * @description
11679 * This method is getter only.
11680 *
11681 * Return full url representation with all segments encoded according to rules specified in
11682 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11683 *
11684 *
11685 * ```js
11686 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11687 * var absUrl = $location.absUrl();
11688 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11689 * ```
11690 *
11691 * @return {string} full url
11692 */
11693 absUrl: locationGetter('$$absUrl'),
11694
11695 /**
11696 * @ngdoc method
11697 * @name $location#url
11698 *
11699 * @description
11700 * This method is getter / setter.
11701 *
11702 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11703 *
11704 * Change path, search and hash, when called with parameter and return `$location`.
11705 *
11706 *
11707 * ```js
11708 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11709 * var url = $location.url();
11710 * // => "/some/path?foo=bar&baz=xoxo"
11711 * ```
11712 *
11713 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11714 * @return {string} url
11715 */
11716 url: function(url) {
11717 if (isUndefined(url)) {
11718 return this.$$url;
11719 }
11720
11721 var match = PATH_MATCH.exec(url);
11722 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11723 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11724 this.hash(match[5] || '');
11725
11726 return this;
11727 },
11728
11729 /**
11730 * @ngdoc method
11731 * @name $location#protocol
11732 *
11733 * @description
11734 * This method is getter only.
11735 *
11736 * Return protocol of current url.
11737 *
11738 *
11739 * ```js
11740 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11741 * var protocol = $location.protocol();
11742 * // => "http"
11743 * ```
11744 *
11745 * @return {string} protocol of current url
11746 */
11747 protocol: locationGetter('$$protocol'),
11748
11749 /**
11750 * @ngdoc method
11751 * @name $location#host
11752 *
11753 * @description
11754 * This method is getter only.
11755 *
11756 * Return host of current url.
11757 *
11758 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11759 *
11760 *
11761 * ```js
11762 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11763 * var host = $location.host();
11764 * // => "example.com"
11765 *
11766 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11767 * host = $location.host();
11768 * // => "example.com"
11769 * host = location.host;
11770 * // => "example.com:8080"
11771 * ```
11772 *
11773 * @return {string} host of current url.
11774 */
11775 host: locationGetter('$$host'),
11776
11777 /**
11778 * @ngdoc method
11779 * @name $location#port
11780 *
11781 * @description
11782 * This method is getter only.
11783 *
11784 * Return port of current url.
11785 *
11786 *
11787 * ```js
11788 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11789 * var port = $location.port();
11790 * // => 80
11791 * ```
11792 *
11793 * @return {Number} port
11794 */
11795 port: locationGetter('$$port'),
11796
11797 /**
11798 * @ngdoc method
11799 * @name $location#path
11800 *
11801 * @description
11802 * This method is getter / setter.
11803 *
11804 * Return path of current url when called without any parameter.
11805 *
11806 * Change path when called with parameter and return `$location`.
11807 *
11808 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11809 * if it is missing.
11810 *
11811 *
11812 * ```js
11813 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11814 * var path = $location.path();
11815 * // => "/some/path"
11816 * ```
11817 *
11818 * @param {(string|number)=} path New path
11819 * @return {string} path
11820 */
11821 path: locationGetterSetter('$$path', function(path) {
11822 path = path !== null ? path.toString() : '';
11823 return path.charAt(0) == '/' ? path : '/' + path;
11824 }),
11825
11826 /**
11827 * @ngdoc method
11828 * @name $location#search
11829 *
11830 * @description
11831 * This method is getter / setter.
11832 *
11833 * Return search part (as object) of current url when called without any parameter.
11834 *
11835 * Change search part when called with parameter and return `$location`.
11836 *
11837 *
11838 * ```js
11839 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11840 * var searchObject = $location.search();
11841 * // => {foo: 'bar', baz: 'xoxo'}
11842 *
11843 * // set foo to 'yipee'
11844 * $location.search('foo', 'yipee');
11845 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11846 * ```
11847 *
11848 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11849 * hash object.
11850 *
11851 * When called with a single argument the method acts as a setter, setting the `search` component
11852 * of `$location` to the specified value.
11853 *
11854 * If the argument is a hash object containing an array of values, these values will be encoded
11855 * as duplicate search parameters in the url.
11856 *
11857 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11858 * will override only a single search property.
11859 *
11860 * If `paramValue` is an array, it will override the property of the `search` component of
11861 * `$location` specified via the first argument.
11862 *
11863 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11864 *
11865 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11866 * value nor trailing equal sign.
11867 *
11868 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11869 * one or more arguments returns `$location` object itself.
11870 */
11871 search: function(search, paramValue) {
11872 switch (arguments.length) {
11873 case 0:
11874 return this.$$search;
11875 case 1:
11876 if (isString(search) || isNumber(search)) {
11877 search = search.toString();
11878 this.$$search = parseKeyValue(search);
11879 } else if (isObject(search)) {
11880 search = copy(search, {});
11881 // remove object undefined or null properties
11882 forEach(search, function(value, key) {
11883 if (value == null) delete search[key];
11884 });
11885
11886 this.$$search = search;
11887 } else {
11888 throw $locationMinErr('isrcharg',
11889 'The first argument of the `$location#search()` call must be a string or an object.');
11890 }
11891 break;
11892 default:
11893 if (isUndefined(paramValue) || paramValue === null) {
11894 delete this.$$search[search];
11895 } else {
11896 this.$$search[search] = paramValue;
11897 }
11898 }
11899
11900 this.$$compose();
11901 return this;
11902 },
11903
11904 /**
11905 * @ngdoc method
11906 * @name $location#hash
11907 *
11908 * @description
11909 * This method is getter / setter.
11910 *
11911 * Return hash fragment when called without any parameter.
11912 *
11913 * Change hash fragment when called with parameter and return `$location`.
11914 *
11915 *
11916 * ```js
11917 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
11918 * var hash = $location.hash();
11919 * // => "hashValue"
11920 * ```
11921 *
11922 * @param {(string|number)=} hash New hash fragment
11923 * @return {string} hash
11924 */
11925 hash: locationGetterSetter('$$hash', function(hash) {
11926 return hash !== null ? hash.toString() : '';
11927 }),
11928
11929 /**
11930 * @ngdoc method
11931 * @name $location#replace
11932 *
11933 * @description
11934 * If called, all changes to $location during current `$digest` will be replacing current history
11935 * record, instead of adding new one.
11936 */
11937 replace: function() {
11938 this.$$replace = true;
11939 return this;
11940 }
11941};
11942
11943forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
11944 Location.prototype = Object.create(locationPrototype);
11945
11946 /**
11947 * @ngdoc method
11948 * @name $location#state
11949 *
11950 * @description
11951 * This method is getter / setter.
11952 *
11953 * Return the history state object when called without any parameter.
11954 *
11955 * Change the history state object when called with one parameter and return `$location`.
11956 * The state object is later passed to `pushState` or `replaceState`.
11957 *
11958 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
11959 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
11960 * older browsers (like IE9 or Android < 4.0), don't use this method.
11961 *
11962 * @param {object=} state State object for pushState or replaceState
11963 * @return {object} state
11964 */
11965 Location.prototype.state = function(state) {
11966 if (!arguments.length) {
11967 return this.$$state;
11968 }
11969
11970 if (Location !== LocationHtml5Url || !this.$$html5) {
11971 throw $locationMinErr('nostate', 'History API state support is available only ' +
11972 'in HTML5 mode and only in browsers supporting HTML5 History API');
11973 }
11974 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
11975 // but we're changing the $$state reference to $browser.state() during the $digest
11976 // so the modification window is narrow.
11977 this.$$state = isUndefined(state) ? null : state;
11978
11979 return this;
11980 };
11981});
11982
11983
11984function locationGetter(property) {
11985 return function() {
11986 return this[property];
11987 };
11988}
11989
11990
11991function locationGetterSetter(property, preprocess) {
11992 return function(value) {
11993 if (isUndefined(value)) {
11994 return this[property];
11995 }
11996
11997 this[property] = preprocess(value);
11998 this.$$compose();
11999
12000 return this;
12001 };
12002}
12003
12004
12005/**
12006 * @ngdoc service
12007 * @name $location
12008 *
12009 * @requires $rootElement
12010 *
12011 * @description
12012 * The $location service parses the URL in the browser address bar (based on the
12013 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12014 * available to your application. Changes to the URL in the address bar are reflected into
12015 * $location service and changes to $location are reflected into the browser address bar.
12016 *
12017 * **The $location service:**
12018 *
12019 * - Exposes the current URL in the browser address bar, so you can
12020 * - Watch and observe the URL.
12021 * - Change the URL.
12022 * - Synchronizes the URL with the browser when the user
12023 * - Changes the address bar.
12024 * - Clicks the back or forward button (or clicks a History link).
12025 * - Clicks on a link.
12026 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12027 *
12028 * For more information see {@link guide/$location Developer Guide: Using $location}
12029 */
12030
12031/**
12032 * @ngdoc provider
12033 * @name $locationProvider
12034 * @description
12035 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12036 */
12037function $LocationProvider() {
12038 var hashPrefix = '',
12039 html5Mode = {
12040 enabled: false,
12041 requireBase: true,
12042 rewriteLinks: true
12043 };
12044
12045 /**
12046 * @ngdoc method
12047 * @name $locationProvider#hashPrefix
12048 * @description
12049 * @param {string=} prefix Prefix for hash part (containing path and search)
12050 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12051 */
12052 this.hashPrefix = function(prefix) {
12053 if (isDefined(prefix)) {
12054 hashPrefix = prefix;
12055 return this;
12056 } else {
12057 return hashPrefix;
12058 }
12059 };
12060
12061 /**
12062 * @ngdoc method
12063 * @name $locationProvider#html5Mode
12064 * @description
12065 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12066 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12067 * properties:
12068 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12069 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12070 * support `pushState`.
12071 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12072 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12073 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12074 * See the {@link guide/$location $location guide for more information}
12075 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12076 * enables/disables url rewriting for relative links.
12077 *
12078 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12079 */
12080 this.html5Mode = function(mode) {
12081 if (isBoolean(mode)) {
12082 html5Mode.enabled = mode;
12083 return this;
12084 } else if (isObject(mode)) {
12085
12086 if (isBoolean(mode.enabled)) {
12087 html5Mode.enabled = mode.enabled;
12088 }
12089
12090 if (isBoolean(mode.requireBase)) {
12091 html5Mode.requireBase = mode.requireBase;
12092 }
12093
12094 if (isBoolean(mode.rewriteLinks)) {
12095 html5Mode.rewriteLinks = mode.rewriteLinks;
12096 }
12097
12098 return this;
12099 } else {
12100 return html5Mode;
12101 }
12102 };
12103
12104 /**
12105 * @ngdoc event
12106 * @name $location#$locationChangeStart
12107 * @eventType broadcast on root scope
12108 * @description
12109 * Broadcasted before a URL will change.
12110 *
12111 * This change can be prevented by calling
12112 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12113 * details about event object. Upon successful change
12114 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12115 *
12116 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12117 * the browser supports the HTML5 History API.
12118 *
12119 * @param {Object} angularEvent Synthetic event object.
12120 * @param {string} newUrl New URL
12121 * @param {string=} oldUrl URL that was before it was changed.
12122 * @param {string=} newState New history state object
12123 * @param {string=} oldState History state object that was before it was changed.
12124 */
12125
12126 /**
12127 * @ngdoc event
12128 * @name $location#$locationChangeSuccess
12129 * @eventType broadcast on root scope
12130 * @description
12131 * Broadcasted after a URL was changed.
12132 *
12133 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12134 * the browser supports the HTML5 History API.
12135 *
12136 * @param {Object} angularEvent Synthetic event object.
12137 * @param {string} newUrl New URL
12138 * @param {string=} oldUrl URL that was before it was changed.
12139 * @param {string=} newState New history state object
12140 * @param {string=} oldState History state object that was before it was changed.
12141 */
12142
12143 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12144 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12145 var $location,
12146 LocationMode,
12147 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12148 initialUrl = $browser.url(),
12149 appBase;
12150
12151 if (html5Mode.enabled) {
12152 if (!baseHref && html5Mode.requireBase) {
12153 throw $locationMinErr('nobase',
12154 "$location in HTML5 mode requires a <base> tag to be present!");
12155 }
12156 appBase = serverBase(initialUrl) + (baseHref || '/');
12157 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12158 } else {
12159 appBase = stripHash(initialUrl);
12160 LocationMode = LocationHashbangUrl;
12161 }
12162 var appBaseNoFile = stripFile(appBase);
12163
12164 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12165 $location.$$parseLinkUrl(initialUrl, initialUrl);
12166
12167 $location.$$state = $browser.state();
12168
12169 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12170
12171 function setBrowserUrlWithFallback(url, replace, state) {
12172 var oldUrl = $location.url();
12173 var oldState = $location.$$state;
12174 try {
12175 $browser.url(url, replace, state);
12176
12177 // Make sure $location.state() returns referentially identical (not just deeply equal)
12178 // state object; this makes possible quick checking if the state changed in the digest
12179 // loop. Checking deep equality would be too expensive.
12180 $location.$$state = $browser.state();
12181 } catch (e) {
12182 // Restore old values if pushState fails
12183 $location.url(oldUrl);
12184 $location.$$state = oldState;
12185
12186 throw e;
12187 }
12188 }
12189
12190 $rootElement.on('click', function(event) {
12191 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12192 // currently we open nice url link and redirect then
12193
12194 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12195
12196 var elm = jqLite(event.target);
12197
12198 // traverse the DOM up to find first A tag
12199 while (nodeName_(elm[0]) !== 'a') {
12200 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12201 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12202 }
12203
12204 var absHref = elm.prop('href');
12205 // get the actual href attribute - see
12206 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12207 var relHref = elm.attr('href') || elm.attr('xlink:href');
12208
12209 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12210 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12211 // an animation.
12212 absHref = urlResolve(absHref.animVal).href;
12213 }
12214
12215 // Ignore when url is started with javascript: or mailto:
12216 if (IGNORE_URI_REGEXP.test(absHref)) return;
12217
12218 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12219 if ($location.$$parseLinkUrl(absHref, relHref)) {
12220 // We do a preventDefault for all urls that are part of the angular application,
12221 // in html5mode and also without, so that we are able to abort navigation without
12222 // getting double entries in the location history.
12223 event.preventDefault();
12224 // update location manually
12225 if ($location.absUrl() != $browser.url()) {
12226 $rootScope.$apply();
12227 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12228 $window.angular['ff-684208-preventDefault'] = true;
12229 }
12230 }
12231 }
12232 });
12233
12234
12235 // rewrite hashbang url <> html5 url
12236 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12237 $browser.url($location.absUrl(), true);
12238 }
12239
12240 var initializing = true;
12241
12242 // update $location when $browser url changes
12243 $browser.onUrlChange(function(newUrl, newState) {
12244
12245 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12246 // If we are navigating outside of the app then force a reload
12247 $window.location.href = newUrl;
12248 return;
12249 }
12250
12251 $rootScope.$evalAsync(function() {
12252 var oldUrl = $location.absUrl();
12253 var oldState = $location.$$state;
12254 var defaultPrevented;
12255
12256 $location.$$parse(newUrl);
12257 $location.$$state = newState;
12258
12259 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12260 newState, oldState).defaultPrevented;
12261
12262 // if the location was changed by a `$locationChangeStart` handler then stop
12263 // processing this location change
12264 if ($location.absUrl() !== newUrl) return;
12265
12266 if (defaultPrevented) {
12267 $location.$$parse(oldUrl);
12268 $location.$$state = oldState;
12269 setBrowserUrlWithFallback(oldUrl, false, oldState);
12270 } else {
12271 initializing = false;
12272 afterLocationChange(oldUrl, oldState);
12273 }
12274 });
12275 if (!$rootScope.$$phase) $rootScope.$digest();
12276 });
12277
12278 // update browser
12279 $rootScope.$watch(function $locationWatch() {
12280 var oldUrl = trimEmptyHash($browser.url());
12281 var newUrl = trimEmptyHash($location.absUrl());
12282 var oldState = $browser.state();
12283 var currentReplace = $location.$$replace;
12284 var urlOrStateChanged = oldUrl !== newUrl ||
12285 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12286
12287 if (initializing || urlOrStateChanged) {
12288 initializing = false;
12289
12290 $rootScope.$evalAsync(function() {
12291 var newUrl = $location.absUrl();
12292 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12293 $location.$$state, oldState).defaultPrevented;
12294
12295 // if the location was changed by a `$locationChangeStart` handler then stop
12296 // processing this location change
12297 if ($location.absUrl() !== newUrl) return;
12298
12299 if (defaultPrevented) {
12300 $location.$$parse(oldUrl);
12301 $location.$$state = oldState;
12302 } else {
12303 if (urlOrStateChanged) {
12304 setBrowserUrlWithFallback(newUrl, currentReplace,
12305 oldState === $location.$$state ? null : $location.$$state);
12306 }
12307 afterLocationChange(oldUrl, oldState);
12308 }
12309 });
12310 }
12311
12312 $location.$$replace = false;
12313
12314 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12315 // there is a change
12316 });
12317
12318 return $location;
12319
12320 function afterLocationChange(oldUrl, oldState) {
12321 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12322 $location.$$state, oldState);
12323 }
12324}];
12325}
12326
12327/**
12328 * @ngdoc service
12329 * @name $log
12330 * @requires $window
12331 *
12332 * @description
12333 * Simple service for logging. Default implementation safely writes the message
12334 * into the browser's console (if present).
12335 *
12336 * The main purpose of this service is to simplify debugging and troubleshooting.
12337 *
12338 * The default is to log `debug` messages. You can use
12339 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12340 *
12341 * @example
12342 <example module="logExample">
12343 <file name="script.js">
12344 angular.module('logExample', [])
12345 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12346 $scope.$log = $log;
12347 $scope.message = 'Hello World!';
12348 }]);
12349 </file>
12350 <file name="index.html">
12351 <div ng-controller="LogController">
12352 <p>Reload this page with open console, enter text and hit the log button...</p>
12353 <label>Message:
12354 <input type="text" ng-model="message" /></label>
12355 <button ng-click="$log.log(message)">log</button>
12356 <button ng-click="$log.warn(message)">warn</button>
12357 <button ng-click="$log.info(message)">info</button>
12358 <button ng-click="$log.error(message)">error</button>
12359 <button ng-click="$log.debug(message)">debug</button>
12360 </div>
12361 </file>
12362 </example>
12363 */
12364
12365/**
12366 * @ngdoc provider
12367 * @name $logProvider
12368 * @description
12369 * Use the `$logProvider` to configure how the application logs messages
12370 */
12371function $LogProvider() {
12372 var debug = true,
12373 self = this;
12374
12375 /**
12376 * @ngdoc method
12377 * @name $logProvider#debugEnabled
12378 * @description
12379 * @param {boolean=} flag enable or disable debug level messages
12380 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12381 */
12382 this.debugEnabled = function(flag) {
12383 if (isDefined(flag)) {
12384 debug = flag;
12385 return this;
12386 } else {
12387 return debug;
12388 }
12389 };
12390
12391 this.$get = ['$window', function($window) {
12392 return {
12393 /**
12394 * @ngdoc method
12395 * @name $log#log
12396 *
12397 * @description
12398 * Write a log message
12399 */
12400 log: consoleLog('log'),
12401
12402 /**
12403 * @ngdoc method
12404 * @name $log#info
12405 *
12406 * @description
12407 * Write an information message
12408 */
12409 info: consoleLog('info'),
12410
12411 /**
12412 * @ngdoc method
12413 * @name $log#warn
12414 *
12415 * @description
12416 * Write a warning message
12417 */
12418 warn: consoleLog('warn'),
12419
12420 /**
12421 * @ngdoc method
12422 * @name $log#error
12423 *
12424 * @description
12425 * Write an error message
12426 */
12427 error: consoleLog('error'),
12428
12429 /**
12430 * @ngdoc method
12431 * @name $log#debug
12432 *
12433 * @description
12434 * Write a debug message
12435 */
12436 debug: (function() {
12437 var fn = consoleLog('debug');
12438
12439 return function() {
12440 if (debug) {
12441 fn.apply(self, arguments);
12442 }
12443 };
12444 }())
12445 };
12446
12447 function formatError(arg) {
12448 if (arg instanceof Error) {
12449 if (arg.stack) {
12450 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12451 ? 'Error: ' + arg.message + '\n' + arg.stack
12452 : arg.stack;
12453 } else if (arg.sourceURL) {
12454 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12455 }
12456 }
12457 return arg;
12458 }
12459
12460 function consoleLog(type) {
12461 var console = $window.console || {},
12462 logFn = console[type] || console.log || noop,
12463 hasApply = false;
12464
12465 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12466 // The reason behind this is that console.log has type "object" in IE8...
12467 try {
12468 hasApply = !!logFn.apply;
12469 } catch (e) {}
12470
12471 if (hasApply) {
12472 return function() {
12473 var args = [];
12474 forEach(arguments, function(arg) {
12475 args.push(formatError(arg));
12476 });
12477 return logFn.apply(console, args);
12478 };
12479 }
12480
12481 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12482 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12483 return function(arg1, arg2) {
12484 logFn(arg1, arg2 == null ? '' : arg2);
12485 };
12486 }
12487 }];
12488}
12489
12490/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12491 * Any commits to this file should be reviewed with security in mind. *
12492 * Changes to this file can potentially create security vulnerabilities. *
12493 * An approval from 2 Core members with history of modifying *
12494 * this file is required. *
12495 * *
12496 * Does the change somehow allow for arbitrary javascript to be executed? *
12497 * Or allows for someone to change the prototype of built-in objects? *
12498 * Or gives undesired access to variables likes document or window? *
12499 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12500
12501var $parseMinErr = minErr('$parse');
12502
12503// Sandboxing Angular Expressions
12504// ------------------------------
12505// Angular expressions are generally considered safe because these expressions only have direct
12506// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12507// obtaining a reference to native JS functions such as the Function constructor.
12508//
12509// As an example, consider the following Angular expression:
12510//
12511// {}.toString.constructor('alert("evil JS code")')
12512//
12513// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12514// against the expression language, but not to prevent exploits that were enabled by exposing
12515// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12516// practice and therefore we are not even trying to protect against interaction with an object
12517// explicitly exposed in this way.
12518//
12519// In general, it is not possible to access a Window object from an angular expression unless a
12520// window or some DOM object that has a reference to window is published onto a Scope.
12521// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12522// native objects.
12523//
12524// See https://docs.angularjs.org/guide/security
12525
12526
12527function ensureSafeMemberName(name, fullExpression) {
12528 if (name === "__defineGetter__" || name === "__defineSetter__"
12529 || name === "__lookupGetter__" || name === "__lookupSetter__"
12530 || name === "__proto__") {
12531 throw $parseMinErr('isecfld',
12532 'Attempting to access a disallowed field in Angular expressions! '
12533 + 'Expression: {0}', fullExpression);
12534 }
12535 return name;
12536}
12537
12538function getStringValue(name, fullExpression) {
12539 // From the JavaScript docs:
12540 // Property names must be strings. This means that non-string objects cannot be used
12541 // as keys in an object. Any non-string object, including a number, is typecasted
12542 // into a string via the toString method.
12543 //
12544 // So, to ensure that we are checking the same `name` that JavaScript would use,
12545 // we cast it to a string, if possible.
12546 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12547 // this is, this will handle objects that misbehave.
12548 name = name + '';
12549 if (!isString(name)) {
12550 throw $parseMinErr('iseccst',
12551 'Cannot convert object to primitive value! '
12552 + 'Expression: {0}', fullExpression);
12553 }
12554 return name;
12555}
12556
12557function ensureSafeObject(obj, fullExpression) {
12558 // nifty check if obj is Function that is fast and works across iframes and other contexts
12559 if (obj) {
12560 if (obj.constructor === obj) {
12561 throw $parseMinErr('isecfn',
12562 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12563 fullExpression);
12564 } else if (// isWindow(obj)
12565 obj.window === obj) {
12566 throw $parseMinErr('isecwindow',
12567 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12568 fullExpression);
12569 } else if (// isElement(obj)
12570 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12571 throw $parseMinErr('isecdom',
12572 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12573 fullExpression);
12574 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12575 obj === Object) {
12576 throw $parseMinErr('isecobj',
12577 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12578 fullExpression);
12579 }
12580 }
12581 return obj;
12582}
12583
12584var CALL = Function.prototype.call;
12585var APPLY = Function.prototype.apply;
12586var BIND = Function.prototype.bind;
12587
12588function ensureSafeFunction(obj, fullExpression) {
12589 if (obj) {
12590 if (obj.constructor === obj) {
12591 throw $parseMinErr('isecfn',
12592 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12593 fullExpression);
12594 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12595 throw $parseMinErr('isecff',
12596 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12597 fullExpression);
12598 }
12599 }
12600}
12601
12602function ensureSafeAssignContext(obj, fullExpression) {
12603 if (obj) {
12604 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12605 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12606 throw $parseMinErr('isecaf',
12607 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12608 }
12609 }
12610}
12611
12612var OPERATORS = createMap();
12613forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12614var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12615
12616
12617/////////////////////////////////////////
12618
12619
12620/**
12621 * @constructor
12622 */
12623var Lexer = function(options) {
12624 this.options = options;
12625};
12626
12627Lexer.prototype = {
12628 constructor: Lexer,
12629
12630 lex: function(text) {
12631 this.text = text;
12632 this.index = 0;
12633 this.tokens = [];
12634
12635 while (this.index < this.text.length) {
12636 var ch = this.text.charAt(this.index);
12637 if (ch === '"' || ch === "'") {
12638 this.readString(ch);
12639 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12640 this.readNumber();
12641 } else if (this.isIdent(ch)) {
12642 this.readIdent();
12643 } else if (this.is(ch, '(){}[].,;:?')) {
12644 this.tokens.push({index: this.index, text: ch});
12645 this.index++;
12646 } else if (this.isWhitespace(ch)) {
12647 this.index++;
12648 } else {
12649 var ch2 = ch + this.peek();
12650 var ch3 = ch2 + this.peek(2);
12651 var op1 = OPERATORS[ch];
12652 var op2 = OPERATORS[ch2];
12653 var op3 = OPERATORS[ch3];
12654 if (op1 || op2 || op3) {
12655 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12656 this.tokens.push({index: this.index, text: token, operator: true});
12657 this.index += token.length;
12658 } else {
12659 this.throwError('Unexpected next character ', this.index, this.index + 1);
12660 }
12661 }
12662 }
12663 return this.tokens;
12664 },
12665
12666 is: function(ch, chars) {
12667 return chars.indexOf(ch) !== -1;
12668 },
12669
12670 peek: function(i) {
12671 var num = i || 1;
12672 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12673 },
12674
12675 isNumber: function(ch) {
12676 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12677 },
12678
12679 isWhitespace: function(ch) {
12680 // IE treats non-breaking space as \u00A0
12681 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12682 ch === '\n' || ch === '\v' || ch === '\u00A0');
12683 },
12684
12685 isIdent: function(ch) {
12686 return ('a' <= ch && ch <= 'z' ||
12687 'A' <= ch && ch <= 'Z' ||
12688 '_' === ch || ch === '$');
12689 },
12690
12691 isExpOperator: function(ch) {
12692 return (ch === '-' || ch === '+' || this.isNumber(ch));
12693 },
12694
12695 throwError: function(error, start, end) {
12696 end = end || this.index;
12697 var colStr = (isDefined(start)
12698 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12699 : ' ' + end);
12700 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12701 error, colStr, this.text);
12702 },
12703
12704 readNumber: function() {
12705 var number = '';
12706 var start = this.index;
12707 while (this.index < this.text.length) {
12708 var ch = lowercase(this.text.charAt(this.index));
12709 if (ch == '.' || this.isNumber(ch)) {
12710 number += ch;
12711 } else {
12712 var peekCh = this.peek();
12713 if (ch == 'e' && this.isExpOperator(peekCh)) {
12714 number += ch;
12715 } else if (this.isExpOperator(ch) &&
12716 peekCh && this.isNumber(peekCh) &&
12717 number.charAt(number.length - 1) == 'e') {
12718 number += ch;
12719 } else if (this.isExpOperator(ch) &&
12720 (!peekCh || !this.isNumber(peekCh)) &&
12721 number.charAt(number.length - 1) == 'e') {
12722 this.throwError('Invalid exponent');
12723 } else {
12724 break;
12725 }
12726 }
12727 this.index++;
12728 }
12729 this.tokens.push({
12730 index: start,
12731 text: number,
12732 constant: true,
12733 value: Number(number)
12734 });
12735 },
12736
12737 readIdent: function() {
12738 var start = this.index;
12739 while (this.index < this.text.length) {
12740 var ch = this.text.charAt(this.index);
12741 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12742 break;
12743 }
12744 this.index++;
12745 }
12746 this.tokens.push({
12747 index: start,
12748 text: this.text.slice(start, this.index),
12749 identifier: true
12750 });
12751 },
12752
12753 readString: function(quote) {
12754 var start = this.index;
12755 this.index++;
12756 var string = '';
12757 var rawString = quote;
12758 var escape = false;
12759 while (this.index < this.text.length) {
12760 var ch = this.text.charAt(this.index);
12761 rawString += ch;
12762 if (escape) {
12763 if (ch === 'u') {
12764 var hex = this.text.substring(this.index + 1, this.index + 5);
12765 if (!hex.match(/[\da-f]{4}/i)) {
12766 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12767 }
12768 this.index += 4;
12769 string += String.fromCharCode(parseInt(hex, 16));
12770 } else {
12771 var rep = ESCAPE[ch];
12772 string = string + (rep || ch);
12773 }
12774 escape = false;
12775 } else if (ch === '\\') {
12776 escape = true;
12777 } else if (ch === quote) {
12778 this.index++;
12779 this.tokens.push({
12780 index: start,
12781 text: rawString,
12782 constant: true,
12783 value: string
12784 });
12785 return;
12786 } else {
12787 string += ch;
12788 }
12789 this.index++;
12790 }
12791 this.throwError('Unterminated quote', start);
12792 }
12793};
12794
12795var AST = function(lexer, options) {
12796 this.lexer = lexer;
12797 this.options = options;
12798};
12799
12800AST.Program = 'Program';
12801AST.ExpressionStatement = 'ExpressionStatement';
12802AST.AssignmentExpression = 'AssignmentExpression';
12803AST.ConditionalExpression = 'ConditionalExpression';
12804AST.LogicalExpression = 'LogicalExpression';
12805AST.BinaryExpression = 'BinaryExpression';
12806AST.UnaryExpression = 'UnaryExpression';
12807AST.CallExpression = 'CallExpression';
12808AST.MemberExpression = 'MemberExpression';
12809AST.Identifier = 'Identifier';
12810AST.Literal = 'Literal';
12811AST.ArrayExpression = 'ArrayExpression';
12812AST.Property = 'Property';
12813AST.ObjectExpression = 'ObjectExpression';
12814AST.ThisExpression = 'ThisExpression';
12815
12816// Internal use only
12817AST.NGValueParameter = 'NGValueParameter';
12818
12819AST.prototype = {
12820 ast: function(text) {
12821 this.text = text;
12822 this.tokens = this.lexer.lex(text);
12823
12824 var value = this.program();
12825
12826 if (this.tokens.length !== 0) {
12827 this.throwError('is an unexpected token', this.tokens[0]);
12828 }
12829
12830 return value;
12831 },
12832
12833 program: function() {
12834 var body = [];
12835 while (true) {
12836 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12837 body.push(this.expressionStatement());
12838 if (!this.expect(';')) {
12839 return { type: AST.Program, body: body};
12840 }
12841 }
12842 },
12843
12844 expressionStatement: function() {
12845 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12846 },
12847
12848 filterChain: function() {
12849 var left = this.expression();
12850 var token;
12851 while ((token = this.expect('|'))) {
12852 left = this.filter(left);
12853 }
12854 return left;
12855 },
12856
12857 expression: function() {
12858 return this.assignment();
12859 },
12860
12861 assignment: function() {
12862 var result = this.ternary();
12863 if (this.expect('=')) {
12864 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12865 }
12866 return result;
12867 },
12868
12869 ternary: function() {
12870 var test = this.logicalOR();
12871 var alternate;
12872 var consequent;
12873 if (this.expect('?')) {
12874 alternate = this.expression();
12875 if (this.consume(':')) {
12876 consequent = this.expression();
12877 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
12878 }
12879 }
12880 return test;
12881 },
12882
12883 logicalOR: function() {
12884 var left = this.logicalAND();
12885 while (this.expect('||')) {
12886 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
12887 }
12888 return left;
12889 },
12890
12891 logicalAND: function() {
12892 var left = this.equality();
12893 while (this.expect('&&')) {
12894 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
12895 }
12896 return left;
12897 },
12898
12899 equality: function() {
12900 var left = this.relational();
12901 var token;
12902 while ((token = this.expect('==','!=','===','!=='))) {
12903 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
12904 }
12905 return left;
12906 },
12907
12908 relational: function() {
12909 var left = this.additive();
12910 var token;
12911 while ((token = this.expect('<', '>', '<=', '>='))) {
12912 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
12913 }
12914 return left;
12915 },
12916
12917 additive: function() {
12918 var left = this.multiplicative();
12919 var token;
12920 while ((token = this.expect('+','-'))) {
12921 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
12922 }
12923 return left;
12924 },
12925
12926 multiplicative: function() {
12927 var left = this.unary();
12928 var token;
12929 while ((token = this.expect('*','/','%'))) {
12930 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
12931 }
12932 return left;
12933 },
12934
12935 unary: function() {
12936 var token;
12937 if ((token = this.expect('+', '-', '!'))) {
12938 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
12939 } else {
12940 return this.primary();
12941 }
12942 },
12943
12944 primary: function() {
12945 var primary;
12946 if (this.expect('(')) {
12947 primary = this.filterChain();
12948 this.consume(')');
12949 } else if (this.expect('[')) {
12950 primary = this.arrayDeclaration();
12951 } else if (this.expect('{')) {
12952 primary = this.object();
12953 } else if (this.constants.hasOwnProperty(this.peek().text)) {
12954 primary = copy(this.constants[this.consume().text]);
12955 } else if (this.peek().identifier) {
12956 primary = this.identifier();
12957 } else if (this.peek().constant) {
12958 primary = this.constant();
12959 } else {
12960 this.throwError('not a primary expression', this.peek());
12961 }
12962
12963 var next;
12964 while ((next = this.expect('(', '[', '.'))) {
12965 if (next.text === '(') {
12966 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
12967 this.consume(')');
12968 } else if (next.text === '[') {
12969 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
12970 this.consume(']');
12971 } else if (next.text === '.') {
12972 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
12973 } else {
12974 this.throwError('IMPOSSIBLE');
12975 }
12976 }
12977 return primary;
12978 },
12979
12980 filter: function(baseExpression) {
12981 var args = [baseExpression];
12982 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
12983
12984 while (this.expect(':')) {
12985 args.push(this.expression());
12986 }
12987
12988 return result;
12989 },
12990
12991 parseArguments: function() {
12992 var args = [];
12993 if (this.peekToken().text !== ')') {
12994 do {
12995 args.push(this.expression());
12996 } while (this.expect(','));
12997 }
12998 return args;
12999 },
13000
13001 identifier: function() {
13002 var token = this.consume();
13003 if (!token.identifier) {
13004 this.throwError('is not a valid identifier', token);
13005 }
13006 return { type: AST.Identifier, name: token.text };
13007 },
13008
13009 constant: function() {
13010 // TODO check that it is a constant
13011 return { type: AST.Literal, value: this.consume().value };
13012 },
13013
13014 arrayDeclaration: function() {
13015 var elements = [];
13016 if (this.peekToken().text !== ']') {
13017 do {
13018 if (this.peek(']')) {
13019 // Support trailing commas per ES5.1.
13020 break;
13021 }
13022 elements.push(this.expression());
13023 } while (this.expect(','));
13024 }
13025 this.consume(']');
13026
13027 return { type: AST.ArrayExpression, elements: elements };
13028 },
13029
13030 object: function() {
13031 var properties = [], property;
13032 if (this.peekToken().text !== '}') {
13033 do {
13034 if (this.peek('}')) {
13035 // Support trailing commas per ES5.1.
13036 break;
13037 }
13038 property = {type: AST.Property, kind: 'init'};
13039 if (this.peek().constant) {
13040 property.key = this.constant();
13041 } else if (this.peek().identifier) {
13042 property.key = this.identifier();
13043 } else {
13044 this.throwError("invalid key", this.peek());
13045 }
13046 this.consume(':');
13047 property.value = this.expression();
13048 properties.push(property);
13049 } while (this.expect(','));
13050 }
13051 this.consume('}');
13052
13053 return {type: AST.ObjectExpression, properties: properties };
13054 },
13055
13056 throwError: function(msg, token) {
13057 throw $parseMinErr('syntax',
13058 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13059 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13060 },
13061
13062 consume: function(e1) {
13063 if (this.tokens.length === 0) {
13064 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13065 }
13066
13067 var token = this.expect(e1);
13068 if (!token) {
13069 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13070 }
13071 return token;
13072 },
13073
13074 peekToken: function() {
13075 if (this.tokens.length === 0) {
13076 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13077 }
13078 return this.tokens[0];
13079 },
13080
13081 peek: function(e1, e2, e3, e4) {
13082 return this.peekAhead(0, e1, e2, e3, e4);
13083 },
13084
13085 peekAhead: function(i, e1, e2, e3, e4) {
13086 if (this.tokens.length > i) {
13087 var token = this.tokens[i];
13088 var t = token.text;
13089 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13090 (!e1 && !e2 && !e3 && !e4)) {
13091 return token;
13092 }
13093 }
13094 return false;
13095 },
13096
13097 expect: function(e1, e2, e3, e4) {
13098 var token = this.peek(e1, e2, e3, e4);
13099 if (token) {
13100 this.tokens.shift();
13101 return token;
13102 }
13103 return false;
13104 },
13105
13106
13107 /* `undefined` is not a constant, it is an identifier,
13108 * but using it as an identifier is not supported
13109 */
13110 constants: {
13111 'true': { type: AST.Literal, value: true },
13112 'false': { type: AST.Literal, value: false },
13113 'null': { type: AST.Literal, value: null },
13114 'undefined': {type: AST.Literal, value: undefined },
13115 'this': {type: AST.ThisExpression }
13116 }
13117};
13118
13119function ifDefined(v, d) {
13120 return typeof v !== 'undefined' ? v : d;
13121}
13122
13123function plusFn(l, r) {
13124 if (typeof l === 'undefined') return r;
13125 if (typeof r === 'undefined') return l;
13126 return l + r;
13127}
13128
13129function isStateless($filter, filterName) {
13130 var fn = $filter(filterName);
13131 return !fn.$stateful;
13132}
13133
13134function findConstantAndWatchExpressions(ast, $filter) {
13135 var allConstants;
13136 var argsToWatch;
13137 switch (ast.type) {
13138 case AST.Program:
13139 allConstants = true;
13140 forEach(ast.body, function(expr) {
13141 findConstantAndWatchExpressions(expr.expression, $filter);
13142 allConstants = allConstants && expr.expression.constant;
13143 });
13144 ast.constant = allConstants;
13145 break;
13146 case AST.Literal:
13147 ast.constant = true;
13148 ast.toWatch = [];
13149 break;
13150 case AST.UnaryExpression:
13151 findConstantAndWatchExpressions(ast.argument, $filter);
13152 ast.constant = ast.argument.constant;
13153 ast.toWatch = ast.argument.toWatch;
13154 break;
13155 case AST.BinaryExpression:
13156 findConstantAndWatchExpressions(ast.left, $filter);
13157 findConstantAndWatchExpressions(ast.right, $filter);
13158 ast.constant = ast.left.constant && ast.right.constant;
13159 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13160 break;
13161 case AST.LogicalExpression:
13162 findConstantAndWatchExpressions(ast.left, $filter);
13163 findConstantAndWatchExpressions(ast.right, $filter);
13164 ast.constant = ast.left.constant && ast.right.constant;
13165 ast.toWatch = ast.constant ? [] : [ast];
13166 break;
13167 case AST.ConditionalExpression:
13168 findConstantAndWatchExpressions(ast.test, $filter);
13169 findConstantAndWatchExpressions(ast.alternate, $filter);
13170 findConstantAndWatchExpressions(ast.consequent, $filter);
13171 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13172 ast.toWatch = ast.constant ? [] : [ast];
13173 break;
13174 case AST.Identifier:
13175 ast.constant = false;
13176 ast.toWatch = [ast];
13177 break;
13178 case AST.MemberExpression:
13179 findConstantAndWatchExpressions(ast.object, $filter);
13180 if (ast.computed) {
13181 findConstantAndWatchExpressions(ast.property, $filter);
13182 }
13183 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13184 ast.toWatch = [ast];
13185 break;
13186 case AST.CallExpression:
13187 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13188 argsToWatch = [];
13189 forEach(ast.arguments, function(expr) {
13190 findConstantAndWatchExpressions(expr, $filter);
13191 allConstants = allConstants && expr.constant;
13192 if (!expr.constant) {
13193 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13194 }
13195 });
13196 ast.constant = allConstants;
13197 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13198 break;
13199 case AST.AssignmentExpression:
13200 findConstantAndWatchExpressions(ast.left, $filter);
13201 findConstantAndWatchExpressions(ast.right, $filter);
13202 ast.constant = ast.left.constant && ast.right.constant;
13203 ast.toWatch = [ast];
13204 break;
13205 case AST.ArrayExpression:
13206 allConstants = true;
13207 argsToWatch = [];
13208 forEach(ast.elements, function(expr) {
13209 findConstantAndWatchExpressions(expr, $filter);
13210 allConstants = allConstants && expr.constant;
13211 if (!expr.constant) {
13212 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13213 }
13214 });
13215 ast.constant = allConstants;
13216 ast.toWatch = argsToWatch;
13217 break;
13218 case AST.ObjectExpression:
13219 allConstants = true;
13220 argsToWatch = [];
13221 forEach(ast.properties, function(property) {
13222 findConstantAndWatchExpressions(property.value, $filter);
13223 allConstants = allConstants && property.value.constant;
13224 if (!property.value.constant) {
13225 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13226 }
13227 });
13228 ast.constant = allConstants;
13229 ast.toWatch = argsToWatch;
13230 break;
13231 case AST.ThisExpression:
13232 ast.constant = false;
13233 ast.toWatch = [];
13234 break;
13235 }
13236}
13237
13238function getInputs(body) {
13239 if (body.length != 1) return;
13240 var lastExpression = body[0].expression;
13241 var candidate = lastExpression.toWatch;
13242 if (candidate.length !== 1) return candidate;
13243 return candidate[0] !== lastExpression ? candidate : undefined;
13244}
13245
13246function isAssignable(ast) {
13247 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13248}
13249
13250function assignableAST(ast) {
13251 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13252 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13253 }
13254}
13255
13256function isLiteral(ast) {
13257 return ast.body.length === 0 ||
13258 ast.body.length === 1 && (
13259 ast.body[0].expression.type === AST.Literal ||
13260 ast.body[0].expression.type === AST.ArrayExpression ||
13261 ast.body[0].expression.type === AST.ObjectExpression);
13262}
13263
13264function isConstant(ast) {
13265 return ast.constant;
13266}
13267
13268function ASTCompiler(astBuilder, $filter) {
13269 this.astBuilder = astBuilder;
13270 this.$filter = $filter;
13271}
13272
13273ASTCompiler.prototype = {
13274 compile: function(expression, expensiveChecks) {
13275 var self = this;
13276 var ast = this.astBuilder.ast(expression);
13277 this.state = {
13278 nextId: 0,
13279 filters: {},
13280 expensiveChecks: expensiveChecks,
13281 fn: {vars: [], body: [], own: {}},
13282 assign: {vars: [], body: [], own: {}},
13283 inputs: []
13284 };
13285 findConstantAndWatchExpressions(ast, self.$filter);
13286 var extra = '';
13287 var assignable;
13288 this.stage = 'assign';
13289 if ((assignable = assignableAST(ast))) {
13290 this.state.computing = 'assign';
13291 var result = this.nextId();
13292 this.recurse(assignable, result);
13293 this.return_(result);
13294 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13295 }
13296 var toWatch = getInputs(ast.body);
13297 self.stage = 'inputs';
13298 forEach(toWatch, function(watch, key) {
13299 var fnKey = 'fn' + key;
13300 self.state[fnKey] = {vars: [], body: [], own: {}};
13301 self.state.computing = fnKey;
13302 var intoId = self.nextId();
13303 self.recurse(watch, intoId);
13304 self.return_(intoId);
13305 self.state.inputs.push(fnKey);
13306 watch.watchId = key;
13307 });
13308 this.state.computing = 'fn';
13309 this.stage = 'main';
13310 this.recurse(ast);
13311 var fnString =
13312 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13313 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13314 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13315 this.filterPrefix() +
13316 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13317 extra +
13318 this.watchFns() +
13319 'return fn;';
13320
13321 /* jshint -W054 */
13322 var fn = (new Function('$filter',
13323 'ensureSafeMemberName',
13324 'ensureSafeObject',
13325 'ensureSafeFunction',
13326 'getStringValue',
13327 'ensureSafeAssignContext',
13328 'ifDefined',
13329 'plus',
13330 'text',
13331 fnString))(
13332 this.$filter,
13333 ensureSafeMemberName,
13334 ensureSafeObject,
13335 ensureSafeFunction,
13336 getStringValue,
13337 ensureSafeAssignContext,
13338 ifDefined,
13339 plusFn,
13340 expression);
13341 /* jshint +W054 */
13342 this.state = this.stage = undefined;
13343 fn.literal = isLiteral(ast);
13344 fn.constant = isConstant(ast);
13345 return fn;
13346 },
13347
13348 USE: 'use',
13349
13350 STRICT: 'strict',
13351
13352 watchFns: function() {
13353 var result = [];
13354 var fns = this.state.inputs;
13355 var self = this;
13356 forEach(fns, function(name) {
13357 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13358 });
13359 if (fns.length) {
13360 result.push('fn.inputs=[' + fns.join(',') + '];');
13361 }
13362 return result.join('');
13363 },
13364
13365 generateFunction: function(name, params) {
13366 return 'function(' + params + '){' +
13367 this.varsPrefix(name) +
13368 this.body(name) +
13369 '};';
13370 },
13371
13372 filterPrefix: function() {
13373 var parts = [];
13374 var self = this;
13375 forEach(this.state.filters, function(id, filter) {
13376 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13377 });
13378 if (parts.length) return 'var ' + parts.join(',') + ';';
13379 return '';
13380 },
13381
13382 varsPrefix: function(section) {
13383 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13384 },
13385
13386 body: function(section) {
13387 return this.state[section].body.join('');
13388 },
13389
13390 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13391 var left, right, self = this, args, expression;
13392 recursionFn = recursionFn || noop;
13393 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13394 intoId = intoId || this.nextId();
13395 this.if_('i',
13396 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13397 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13398 );
13399 return;
13400 }
13401 switch (ast.type) {
13402 case AST.Program:
13403 forEach(ast.body, function(expression, pos) {
13404 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13405 if (pos !== ast.body.length - 1) {
13406 self.current().body.push(right, ';');
13407 } else {
13408 self.return_(right);
13409 }
13410 });
13411 break;
13412 case AST.Literal:
13413 expression = this.escape(ast.value);
13414 this.assign(intoId, expression);
13415 recursionFn(expression);
13416 break;
13417 case AST.UnaryExpression:
13418 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13419 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13420 this.assign(intoId, expression);
13421 recursionFn(expression);
13422 break;
13423 case AST.BinaryExpression:
13424 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13425 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13426 if (ast.operator === '+') {
13427 expression = this.plus(left, right);
13428 } else if (ast.operator === '-') {
13429 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13430 } else {
13431 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13432 }
13433 this.assign(intoId, expression);
13434 recursionFn(expression);
13435 break;
13436 case AST.LogicalExpression:
13437 intoId = intoId || this.nextId();
13438 self.recurse(ast.left, intoId);
13439 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13440 recursionFn(intoId);
13441 break;
13442 case AST.ConditionalExpression:
13443 intoId = intoId || this.nextId();
13444 self.recurse(ast.test, intoId);
13445 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13446 recursionFn(intoId);
13447 break;
13448 case AST.Identifier:
13449 intoId = intoId || this.nextId();
13450 if (nameId) {
13451 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13452 nameId.computed = false;
13453 nameId.name = ast.name;
13454 }
13455 ensureSafeMemberName(ast.name);
13456 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13457 function() {
13458 self.if_(self.stage === 'inputs' || 's', function() {
13459 if (create && create !== 1) {
13460 self.if_(
13461 self.not(self.nonComputedMember('s', ast.name)),
13462 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13463 }
13464 self.assign(intoId, self.nonComputedMember('s', ast.name));
13465 });
13466 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13467 );
13468 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13469 self.addEnsureSafeObject(intoId);
13470 }
13471 recursionFn(intoId);
13472 break;
13473 case AST.MemberExpression:
13474 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13475 intoId = intoId || this.nextId();
13476 self.recurse(ast.object, left, undefined, function() {
13477 self.if_(self.notNull(left), function() {
13478 if (ast.computed) {
13479 right = self.nextId();
13480 self.recurse(ast.property, right);
13481 self.getStringValue(right);
13482 self.addEnsureSafeMemberName(right);
13483 if (create && create !== 1) {
13484 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13485 }
13486 expression = self.ensureSafeObject(self.computedMember(left, right));
13487 self.assign(intoId, expression);
13488 if (nameId) {
13489 nameId.computed = true;
13490 nameId.name = right;
13491 }
13492 } else {
13493 ensureSafeMemberName(ast.property.name);
13494 if (create && create !== 1) {
13495 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13496 }
13497 expression = self.nonComputedMember(left, ast.property.name);
13498 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13499 expression = self.ensureSafeObject(expression);
13500 }
13501 self.assign(intoId, expression);
13502 if (nameId) {
13503 nameId.computed = false;
13504 nameId.name = ast.property.name;
13505 }
13506 }
13507 }, function() {
13508 self.assign(intoId, 'undefined');
13509 });
13510 recursionFn(intoId);
13511 }, !!create);
13512 break;
13513 case AST.CallExpression:
13514 intoId = intoId || this.nextId();
13515 if (ast.filter) {
13516 right = self.filter(ast.callee.name);
13517 args = [];
13518 forEach(ast.arguments, function(expr) {
13519 var argument = self.nextId();
13520 self.recurse(expr, argument);
13521 args.push(argument);
13522 });
13523 expression = right + '(' + args.join(',') + ')';
13524 self.assign(intoId, expression);
13525 recursionFn(intoId);
13526 } else {
13527 right = self.nextId();
13528 left = {};
13529 args = [];
13530 self.recurse(ast.callee, right, left, function() {
13531 self.if_(self.notNull(right), function() {
13532 self.addEnsureSafeFunction(right);
13533 forEach(ast.arguments, function(expr) {
13534 self.recurse(expr, self.nextId(), undefined, function(argument) {
13535 args.push(self.ensureSafeObject(argument));
13536 });
13537 });
13538 if (left.name) {
13539 if (!self.state.expensiveChecks) {
13540 self.addEnsureSafeObject(left.context);
13541 }
13542 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13543 } else {
13544 expression = right + '(' + args.join(',') + ')';
13545 }
13546 expression = self.ensureSafeObject(expression);
13547 self.assign(intoId, expression);
13548 }, function() {
13549 self.assign(intoId, 'undefined');
13550 });
13551 recursionFn(intoId);
13552 });
13553 }
13554 break;
13555 case AST.AssignmentExpression:
13556 right = this.nextId();
13557 left = {};
13558 if (!isAssignable(ast.left)) {
13559 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13560 }
13561 this.recurse(ast.left, undefined, left, function() {
13562 self.if_(self.notNull(left.context), function() {
13563 self.recurse(ast.right, right);
13564 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13565 self.addEnsureSafeAssignContext(left.context);
13566 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13567 self.assign(intoId, expression);
13568 recursionFn(intoId || expression);
13569 });
13570 }, 1);
13571 break;
13572 case AST.ArrayExpression:
13573 args = [];
13574 forEach(ast.elements, function(expr) {
13575 self.recurse(expr, self.nextId(), undefined, function(argument) {
13576 args.push(argument);
13577 });
13578 });
13579 expression = '[' + args.join(',') + ']';
13580 this.assign(intoId, expression);
13581 recursionFn(expression);
13582 break;
13583 case AST.ObjectExpression:
13584 args = [];
13585 forEach(ast.properties, function(property) {
13586 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13587 args.push(self.escape(
13588 property.key.type === AST.Identifier ? property.key.name :
13589 ('' + property.key.value)) +
13590 ':' + expr);
13591 });
13592 });
13593 expression = '{' + args.join(',') + '}';
13594 this.assign(intoId, expression);
13595 recursionFn(expression);
13596 break;
13597 case AST.ThisExpression:
13598 this.assign(intoId, 's');
13599 recursionFn('s');
13600 break;
13601 case AST.NGValueParameter:
13602 this.assign(intoId, 'v');
13603 recursionFn('v');
13604 break;
13605 }
13606 },
13607
13608 getHasOwnProperty: function(element, property) {
13609 var key = element + '.' + property;
13610 var own = this.current().own;
13611 if (!own.hasOwnProperty(key)) {
13612 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13613 }
13614 return own[key];
13615 },
13616
13617 assign: function(id, value) {
13618 if (!id) return;
13619 this.current().body.push(id, '=', value, ';');
13620 return id;
13621 },
13622
13623 filter: function(filterName) {
13624 if (!this.state.filters.hasOwnProperty(filterName)) {
13625 this.state.filters[filterName] = this.nextId(true);
13626 }
13627 return this.state.filters[filterName];
13628 },
13629
13630 ifDefined: function(id, defaultValue) {
13631 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13632 },
13633
13634 plus: function(left, right) {
13635 return 'plus(' + left + ',' + right + ')';
13636 },
13637
13638 return_: function(id) {
13639 this.current().body.push('return ', id, ';');
13640 },
13641
13642 if_: function(test, alternate, consequent) {
13643 if (test === true) {
13644 alternate();
13645 } else {
13646 var body = this.current().body;
13647 body.push('if(', test, '){');
13648 alternate();
13649 body.push('}');
13650 if (consequent) {
13651 body.push('else{');
13652 consequent();
13653 body.push('}');
13654 }
13655 }
13656 },
13657
13658 not: function(expression) {
13659 return '!(' + expression + ')';
13660 },
13661
13662 notNull: function(expression) {
13663 return expression + '!=null';
13664 },
13665
13666 nonComputedMember: function(left, right) {
13667 return left + '.' + right;
13668 },
13669
13670 computedMember: function(left, right) {
13671 return left + '[' + right + ']';
13672 },
13673
13674 member: function(left, right, computed) {
13675 if (computed) return this.computedMember(left, right);
13676 return this.nonComputedMember(left, right);
13677 },
13678
13679 addEnsureSafeObject: function(item) {
13680 this.current().body.push(this.ensureSafeObject(item), ';');
13681 },
13682
13683 addEnsureSafeMemberName: function(item) {
13684 this.current().body.push(this.ensureSafeMemberName(item), ';');
13685 },
13686
13687 addEnsureSafeFunction: function(item) {
13688 this.current().body.push(this.ensureSafeFunction(item), ';');
13689 },
13690
13691 addEnsureSafeAssignContext: function(item) {
13692 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13693 },
13694
13695 ensureSafeObject: function(item) {
13696 return 'ensureSafeObject(' + item + ',text)';
13697 },
13698
13699 ensureSafeMemberName: function(item) {
13700 return 'ensureSafeMemberName(' + item + ',text)';
13701 },
13702
13703 ensureSafeFunction: function(item) {
13704 return 'ensureSafeFunction(' + item + ',text)';
13705 },
13706
13707 getStringValue: function(item) {
13708 this.assign(item, 'getStringValue(' + item + ',text)');
13709 },
13710
13711 ensureSafeAssignContext: function(item) {
13712 return 'ensureSafeAssignContext(' + item + ',text)';
13713 },
13714
13715 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13716 var self = this;
13717 return function() {
13718 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13719 };
13720 },
13721
13722 lazyAssign: function(id, value) {
13723 var self = this;
13724 return function() {
13725 self.assign(id, value);
13726 };
13727 },
13728
13729 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13730
13731 stringEscapeFn: function(c) {
13732 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13733 },
13734
13735 escape: function(value) {
13736 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13737 if (isNumber(value)) return value.toString();
13738 if (value === true) return 'true';
13739 if (value === false) return 'false';
13740 if (value === null) return 'null';
13741 if (typeof value === 'undefined') return 'undefined';
13742
13743 throw $parseMinErr('esc', 'IMPOSSIBLE');
13744 },
13745
13746 nextId: function(skip, init) {
13747 var id = 'v' + (this.state.nextId++);
13748 if (!skip) {
13749 this.current().vars.push(id + (init ? '=' + init : ''));
13750 }
13751 return id;
13752 },
13753
13754 current: function() {
13755 return this.state[this.state.computing];
13756 }
13757};
13758
13759
13760function ASTInterpreter(astBuilder, $filter) {
13761 this.astBuilder = astBuilder;
13762 this.$filter = $filter;
13763}
13764
13765ASTInterpreter.prototype = {
13766 compile: function(expression, expensiveChecks) {
13767 var self = this;
13768 var ast = this.astBuilder.ast(expression);
13769 this.expression = expression;
13770 this.expensiveChecks = expensiveChecks;
13771 findConstantAndWatchExpressions(ast, self.$filter);
13772 var assignable;
13773 var assign;
13774 if ((assignable = assignableAST(ast))) {
13775 assign = this.recurse(assignable);
13776 }
13777 var toWatch = getInputs(ast.body);
13778 var inputs;
13779 if (toWatch) {
13780 inputs = [];
13781 forEach(toWatch, function(watch, key) {
13782 var input = self.recurse(watch);
13783 watch.input = input;
13784 inputs.push(input);
13785 watch.watchId = key;
13786 });
13787 }
13788 var expressions = [];
13789 forEach(ast.body, function(expression) {
13790 expressions.push(self.recurse(expression.expression));
13791 });
13792 var fn = ast.body.length === 0 ? function() {} :
13793 ast.body.length === 1 ? expressions[0] :
13794 function(scope, locals) {
13795 var lastValue;
13796 forEach(expressions, function(exp) {
13797 lastValue = exp(scope, locals);
13798 });
13799 return lastValue;
13800 };
13801 if (assign) {
13802 fn.assign = function(scope, value, locals) {
13803 return assign(scope, locals, value);
13804 };
13805 }
13806 if (inputs) {
13807 fn.inputs = inputs;
13808 }
13809 fn.literal = isLiteral(ast);
13810 fn.constant = isConstant(ast);
13811 return fn;
13812 },
13813
13814 recurse: function(ast, context, create) {
13815 var left, right, self = this, args, expression;
13816 if (ast.input) {
13817 return this.inputs(ast.input, ast.watchId);
13818 }
13819 switch (ast.type) {
13820 case AST.Literal:
13821 return this.value(ast.value, context);
13822 case AST.UnaryExpression:
13823 right = this.recurse(ast.argument);
13824 return this['unary' + ast.operator](right, context);
13825 case AST.BinaryExpression:
13826 left = this.recurse(ast.left);
13827 right = this.recurse(ast.right);
13828 return this['binary' + ast.operator](left, right, context);
13829 case AST.LogicalExpression:
13830 left = this.recurse(ast.left);
13831 right = this.recurse(ast.right);
13832 return this['binary' + ast.operator](left, right, context);
13833 case AST.ConditionalExpression:
13834 return this['ternary?:'](
13835 this.recurse(ast.test),
13836 this.recurse(ast.alternate),
13837 this.recurse(ast.consequent),
13838 context
13839 );
13840 case AST.Identifier:
13841 ensureSafeMemberName(ast.name, self.expression);
13842 return self.identifier(ast.name,
13843 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13844 context, create, self.expression);
13845 case AST.MemberExpression:
13846 left = this.recurse(ast.object, false, !!create);
13847 if (!ast.computed) {
13848 ensureSafeMemberName(ast.property.name, self.expression);
13849 right = ast.property.name;
13850 }
13851 if (ast.computed) right = this.recurse(ast.property);
13852 return ast.computed ?
13853 this.computedMember(left, right, context, create, self.expression) :
13854 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13855 case AST.CallExpression:
13856 args = [];
13857 forEach(ast.arguments, function(expr) {
13858 args.push(self.recurse(expr));
13859 });
13860 if (ast.filter) right = this.$filter(ast.callee.name);
13861 if (!ast.filter) right = this.recurse(ast.callee, true);
13862 return ast.filter ?
13863 function(scope, locals, assign, inputs) {
13864 var values = [];
13865 for (var i = 0; i < args.length; ++i) {
13866 values.push(args[i](scope, locals, assign, inputs));
13867 }
13868 var value = right.apply(undefined, values, inputs);
13869 return context ? {context: undefined, name: undefined, value: value} : value;
13870 } :
13871 function(scope, locals, assign, inputs) {
13872 var rhs = right(scope, locals, assign, inputs);
13873 var value;
13874 if (rhs.value != null) {
13875 ensureSafeObject(rhs.context, self.expression);
13876 ensureSafeFunction(rhs.value, self.expression);
13877 var values = [];
13878 for (var i = 0; i < args.length; ++i) {
13879 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
13880 }
13881 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
13882 }
13883 return context ? {value: value} : value;
13884 };
13885 case AST.AssignmentExpression:
13886 left = this.recurse(ast.left, true, 1);
13887 right = this.recurse(ast.right);
13888 return function(scope, locals, assign, inputs) {
13889 var lhs = left(scope, locals, assign, inputs);
13890 var rhs = right(scope, locals, assign, inputs);
13891 ensureSafeObject(lhs.value, self.expression);
13892 ensureSafeAssignContext(lhs.context);
13893 lhs.context[lhs.name] = rhs;
13894 return context ? {value: rhs} : rhs;
13895 };
13896 case AST.ArrayExpression:
13897 args = [];
13898 forEach(ast.elements, function(expr) {
13899 args.push(self.recurse(expr));
13900 });
13901 return function(scope, locals, assign, inputs) {
13902 var value = [];
13903 for (var i = 0; i < args.length; ++i) {
13904 value.push(args[i](scope, locals, assign, inputs));
13905 }
13906 return context ? {value: value} : value;
13907 };
13908 case AST.ObjectExpression:
13909 args = [];
13910 forEach(ast.properties, function(property) {
13911 args.push({key: property.key.type === AST.Identifier ?
13912 property.key.name :
13913 ('' + property.key.value),
13914 value: self.recurse(property.value)
13915 });
13916 });
13917 return function(scope, locals, assign, inputs) {
13918 var value = {};
13919 for (var i = 0; i < args.length; ++i) {
13920 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
13921 }
13922 return context ? {value: value} : value;
13923 };
13924 case AST.ThisExpression:
13925 return function(scope) {
13926 return context ? {value: scope} : scope;
13927 };
13928 case AST.NGValueParameter:
13929 return function(scope, locals, assign, inputs) {
13930 return context ? {value: assign} : assign;
13931 };
13932 }
13933 },
13934
13935 'unary+': function(argument, context) {
13936 return function(scope, locals, assign, inputs) {
13937 var arg = argument(scope, locals, assign, inputs);
13938 if (isDefined(arg)) {
13939 arg = +arg;
13940 } else {
13941 arg = 0;
13942 }
13943 return context ? {value: arg} : arg;
13944 };
13945 },
13946 'unary-': function(argument, context) {
13947 return function(scope, locals, assign, inputs) {
13948 var arg = argument(scope, locals, assign, inputs);
13949 if (isDefined(arg)) {
13950 arg = -arg;
13951 } else {
13952 arg = 0;
13953 }
13954 return context ? {value: arg} : arg;
13955 };
13956 },
13957 'unary!': function(argument, context) {
13958 return function(scope, locals, assign, inputs) {
13959 var arg = !argument(scope, locals, assign, inputs);
13960 return context ? {value: arg} : arg;
13961 };
13962 },
13963 'binary+': function(left, right, context) {
13964 return function(scope, locals, assign, inputs) {
13965 var lhs = left(scope, locals, assign, inputs);
13966 var rhs = right(scope, locals, assign, inputs);
13967 var arg = plusFn(lhs, rhs);
13968 return context ? {value: arg} : arg;
13969 };
13970 },
13971 'binary-': function(left, right, context) {
13972 return function(scope, locals, assign, inputs) {
13973 var lhs = left(scope, locals, assign, inputs);
13974 var rhs = right(scope, locals, assign, inputs);
13975 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
13976 return context ? {value: arg} : arg;
13977 };
13978 },
13979 'binary*': function(left, right, context) {
13980 return function(scope, locals, assign, inputs) {
13981 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
13982 return context ? {value: arg} : arg;
13983 };
13984 },
13985 'binary/': function(left, right, context) {
13986 return function(scope, locals, assign, inputs) {
13987 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
13988 return context ? {value: arg} : arg;
13989 };
13990 },
13991 'binary%': function(left, right, context) {
13992 return function(scope, locals, assign, inputs) {
13993 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
13994 return context ? {value: arg} : arg;
13995 };
13996 },
13997 'binary===': function(left, right, context) {
13998 return function(scope, locals, assign, inputs) {
13999 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14000 return context ? {value: arg} : arg;
14001 };
14002 },
14003 'binary!==': function(left, right, context) {
14004 return function(scope, locals, assign, inputs) {
14005 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14006 return context ? {value: arg} : arg;
14007 };
14008 },
14009 'binary==': function(left, right, context) {
14010 return function(scope, locals, assign, inputs) {
14011 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14012 return context ? {value: arg} : arg;
14013 };
14014 },
14015 'binary!=': function(left, right, context) {
14016 return function(scope, locals, assign, inputs) {
14017 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14018 return context ? {value: arg} : arg;
14019 };
14020 },
14021 'binary<': function(left, right, context) {
14022 return function(scope, locals, assign, inputs) {
14023 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14024 return context ? {value: arg} : arg;
14025 };
14026 },
14027 'binary>': function(left, right, context) {
14028 return function(scope, locals, assign, inputs) {
14029 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14030 return context ? {value: arg} : arg;
14031 };
14032 },
14033 'binary<=': function(left, right, context) {
14034 return function(scope, locals, assign, inputs) {
14035 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14036 return context ? {value: arg} : arg;
14037 };
14038 },
14039 'binary>=': function(left, right, context) {
14040 return function(scope, locals, assign, inputs) {
14041 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14042 return context ? {value: arg} : arg;
14043 };
14044 },
14045 'binary&&': function(left, right, context) {
14046 return function(scope, locals, assign, inputs) {
14047 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14048 return context ? {value: arg} : arg;
14049 };
14050 },
14051 'binary||': function(left, right, context) {
14052 return function(scope, locals, assign, inputs) {
14053 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14054 return context ? {value: arg} : arg;
14055 };
14056 },
14057 'ternary?:': function(test, alternate, consequent, context) {
14058 return function(scope, locals, assign, inputs) {
14059 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14060 return context ? {value: arg} : arg;
14061 };
14062 },
14063 value: function(value, context) {
14064 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14065 },
14066 identifier: function(name, expensiveChecks, context, create, expression) {
14067 return function(scope, locals, assign, inputs) {
14068 var base = locals && (name in locals) ? locals : scope;
14069 if (create && create !== 1 && base && !(base[name])) {
14070 base[name] = {};
14071 }
14072 var value = base ? base[name] : undefined;
14073 if (expensiveChecks) {
14074 ensureSafeObject(value, expression);
14075 }
14076 if (context) {
14077 return {context: base, name: name, value: value};
14078 } else {
14079 return value;
14080 }
14081 };
14082 },
14083 computedMember: function(left, right, context, create, expression) {
14084 return function(scope, locals, assign, inputs) {
14085 var lhs = left(scope, locals, assign, inputs);
14086 var rhs;
14087 var value;
14088 if (lhs != null) {
14089 rhs = right(scope, locals, assign, inputs);
14090 rhs = getStringValue(rhs);
14091 ensureSafeMemberName(rhs, expression);
14092 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14093 lhs[rhs] = {};
14094 }
14095 value = lhs[rhs];
14096 ensureSafeObject(value, expression);
14097 }
14098 if (context) {
14099 return {context: lhs, name: rhs, value: value};
14100 } else {
14101 return value;
14102 }
14103 };
14104 },
14105 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14106 return function(scope, locals, assign, inputs) {
14107 var lhs = left(scope, locals, assign, inputs);
14108 if (create && create !== 1 && lhs && !(lhs[right])) {
14109 lhs[right] = {};
14110 }
14111 var value = lhs != null ? lhs[right] : undefined;
14112 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14113 ensureSafeObject(value, expression);
14114 }
14115 if (context) {
14116 return {context: lhs, name: right, value: value};
14117 } else {
14118 return value;
14119 }
14120 };
14121 },
14122 inputs: function(input, watchId) {
14123 return function(scope, value, locals, inputs) {
14124 if (inputs) return inputs[watchId];
14125 return input(scope, value, locals);
14126 };
14127 }
14128};
14129
14130/**
14131 * @constructor
14132 */
14133var Parser = function(lexer, $filter, options) {
14134 this.lexer = lexer;
14135 this.$filter = $filter;
14136 this.options = options;
14137 this.ast = new AST(this.lexer);
14138 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14139 new ASTCompiler(this.ast, $filter);
14140};
14141
14142Parser.prototype = {
14143 constructor: Parser,
14144
14145 parse: function(text) {
14146 return this.astCompiler.compile(text, this.options.expensiveChecks);
14147 }
14148};
14149
14150var getterFnCacheDefault = createMap();
14151var getterFnCacheExpensive = createMap();
14152
14153function isPossiblyDangerousMemberName(name) {
14154 return name == 'constructor';
14155}
14156
14157var objectValueOf = Object.prototype.valueOf;
14158
14159function getValueOf(value) {
14160 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14161}
14162
14163///////////////////////////////////
14164
14165/**
14166 * @ngdoc service
14167 * @name $parse
14168 * @kind function
14169 *
14170 * @description
14171 *
14172 * Converts Angular {@link guide/expression expression} into a function.
14173 *
14174 * ```js
14175 * var getter = $parse('user.name');
14176 * var setter = getter.assign;
14177 * var context = {user:{name:'angular'}};
14178 * var locals = {user:{name:'local'}};
14179 *
14180 * expect(getter(context)).toEqual('angular');
14181 * setter(context, 'newValue');
14182 * expect(context.user.name).toEqual('newValue');
14183 * expect(getter(context, locals)).toEqual('local');
14184 * ```
14185 *
14186 *
14187 * @param {string} expression String expression to compile.
14188 * @returns {function(context, locals)} a function which represents the compiled expression:
14189 *
14190 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14191 * are evaluated against (typically a scope object).
14192 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14193 * `context`.
14194 *
14195 * The returned function also has the following properties:
14196 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14197 * literal.
14198 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14199 * constant literals.
14200 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14201 * set to a function to change its value on the given context.
14202 *
14203 */
14204
14205
14206/**
14207 * @ngdoc provider
14208 * @name $parseProvider
14209 *
14210 * @description
14211 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14212 * service.
14213 */
14214function $ParseProvider() {
14215 var cacheDefault = createMap();
14216 var cacheExpensive = createMap();
14217
14218 this.$get = ['$filter', function($filter) {
14219 var noUnsafeEval = csp().noUnsafeEval;
14220 var $parseOptions = {
14221 csp: noUnsafeEval,
14222 expensiveChecks: false
14223 },
14224 $parseOptionsExpensive = {
14225 csp: noUnsafeEval,
14226 expensiveChecks: true
14227 };
14228
14229 return function $parse(exp, interceptorFn, expensiveChecks) {
14230 var parsedExpression, oneTime, cacheKey;
14231
14232 switch (typeof exp) {
14233 case 'string':
14234 exp = exp.trim();
14235 cacheKey = exp;
14236
14237 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14238 parsedExpression = cache[cacheKey];
14239
14240 if (!parsedExpression) {
14241 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14242 oneTime = true;
14243 exp = exp.substring(2);
14244 }
14245 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14246 var lexer = new Lexer(parseOptions);
14247 var parser = new Parser(lexer, $filter, parseOptions);
14248 parsedExpression = parser.parse(exp);
14249 if (parsedExpression.constant) {
14250 parsedExpression.$$watchDelegate = constantWatchDelegate;
14251 } else if (oneTime) {
14252 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14253 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14254 } else if (parsedExpression.inputs) {
14255 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14256 }
14257 cache[cacheKey] = parsedExpression;
14258 }
14259 return addInterceptor(parsedExpression, interceptorFn);
14260
14261 case 'function':
14262 return addInterceptor(exp, interceptorFn);
14263
14264 default:
14265 return noop;
14266 }
14267 };
14268
14269 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14270
14271 if (newValue == null || oldValueOfValue == null) { // null/undefined
14272 return newValue === oldValueOfValue;
14273 }
14274
14275 if (typeof newValue === 'object') {
14276
14277 // attempt to convert the value to a primitive type
14278 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14279 // be cheaply dirty-checked
14280 newValue = getValueOf(newValue);
14281
14282 if (typeof newValue === 'object') {
14283 // objects/arrays are not supported - deep-watching them would be too expensive
14284 return false;
14285 }
14286
14287 // fall-through to the primitive equality check
14288 }
14289
14290 //Primitive or NaN
14291 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14292 }
14293
14294 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14295 var inputExpressions = parsedExpression.inputs;
14296 var lastResult;
14297
14298 if (inputExpressions.length === 1) {
14299 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14300 inputExpressions = inputExpressions[0];
14301 return scope.$watch(function expressionInputWatch(scope) {
14302 var newInputValue = inputExpressions(scope);
14303 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14304 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14305 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14306 }
14307 return lastResult;
14308 }, listener, objectEquality, prettyPrintExpression);
14309 }
14310
14311 var oldInputValueOfValues = [];
14312 var oldInputValues = [];
14313 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14314 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14315 oldInputValues[i] = null;
14316 }
14317
14318 return scope.$watch(function expressionInputsWatch(scope) {
14319 var changed = false;
14320
14321 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14322 var newInputValue = inputExpressions[i](scope);
14323 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14324 oldInputValues[i] = newInputValue;
14325 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14326 }
14327 }
14328
14329 if (changed) {
14330 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14331 }
14332
14333 return lastResult;
14334 }, listener, objectEquality, prettyPrintExpression);
14335 }
14336
14337 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14338 var unwatch, lastValue;
14339 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14340 return parsedExpression(scope);
14341 }, function oneTimeListener(value, old, scope) {
14342 lastValue = value;
14343 if (isFunction(listener)) {
14344 listener.apply(this, arguments);
14345 }
14346 if (isDefined(value)) {
14347 scope.$$postDigest(function() {
14348 if (isDefined(lastValue)) {
14349 unwatch();
14350 }
14351 });
14352 }
14353 }, objectEquality);
14354 }
14355
14356 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14357 var unwatch, lastValue;
14358 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14359 return parsedExpression(scope);
14360 }, function oneTimeListener(value, old, scope) {
14361 lastValue = value;
14362 if (isFunction(listener)) {
14363 listener.call(this, value, old, scope);
14364 }
14365 if (isAllDefined(value)) {
14366 scope.$$postDigest(function() {
14367 if (isAllDefined(lastValue)) unwatch();
14368 });
14369 }
14370 }, objectEquality);
14371
14372 function isAllDefined(value) {
14373 var allDefined = true;
14374 forEach(value, function(val) {
14375 if (!isDefined(val)) allDefined = false;
14376 });
14377 return allDefined;
14378 }
14379 }
14380
14381 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14382 var unwatch;
14383 return unwatch = scope.$watch(function constantWatch(scope) {
14384 return parsedExpression(scope);
14385 }, function constantListener(value, old, scope) {
14386 if (isFunction(listener)) {
14387 listener.apply(this, arguments);
14388 }
14389 unwatch();
14390 }, objectEquality);
14391 }
14392
14393 function addInterceptor(parsedExpression, interceptorFn) {
14394 if (!interceptorFn) return parsedExpression;
14395 var watchDelegate = parsedExpression.$$watchDelegate;
14396
14397 var regularWatch =
14398 watchDelegate !== oneTimeLiteralWatchDelegate &&
14399 watchDelegate !== oneTimeWatchDelegate;
14400
14401 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14402 var value = parsedExpression(scope, locals, assign, inputs);
14403 return interceptorFn(value, scope, locals);
14404 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14405 var value = parsedExpression(scope, locals, assign, inputs);
14406 var result = interceptorFn(value, scope, locals);
14407 // we only return the interceptor's result if the
14408 // initial value is defined (for bind-once)
14409 return isDefined(value) ? result : value;
14410 };
14411
14412 // Propagate $$watchDelegates other then inputsWatchDelegate
14413 if (parsedExpression.$$watchDelegate &&
14414 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14415 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14416 } else if (!interceptorFn.$stateful) {
14417 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14418 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14419 fn.$$watchDelegate = inputsWatchDelegate;
14420 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14421 }
14422
14423 return fn;
14424 }
14425 }];
14426}
14427
14428/**
14429 * @ngdoc service
14430 * @name $q
14431 * @requires $rootScope
14432 *
14433 * @description
14434 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14435 * when they are done processing.
14436 *
14437 * This is an implementation of promises/deferred objects inspired by
14438 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14439 *
14440 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14441 * implementations, and the other which resembles ES6 promises to some degree.
14442 *
14443 * # $q constructor
14444 *
14445 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14446 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14447 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14448 *
14449 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14450 * available yet.
14451 *
14452 * It can be used like so:
14453 *
14454 * ```js
14455 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14456 * // are available in the current lexical scope (they could have been injected or passed in).
14457 *
14458 * function asyncGreet(name) {
14459 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14460 * return $q(function(resolve, reject) {
14461 * setTimeout(function() {
14462 * if (okToGreet(name)) {
14463 * resolve('Hello, ' + name + '!');
14464 * } else {
14465 * reject('Greeting ' + name + ' is not allowed.');
14466 * }
14467 * }, 1000);
14468 * });
14469 * }
14470 *
14471 * var promise = asyncGreet('Robin Hood');
14472 * promise.then(function(greeting) {
14473 * alert('Success: ' + greeting);
14474 * }, function(reason) {
14475 * alert('Failed: ' + reason);
14476 * });
14477 * ```
14478 *
14479 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14480 *
14481 * However, the more traditional CommonJS-style usage is still available, and documented below.
14482 *
14483 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14484 * interface for interacting with an object that represents the result of an action that is
14485 * performed asynchronously, and may or may not be finished at any given point in time.
14486 *
14487 * From the perspective of dealing with error handling, deferred and promise APIs are to
14488 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14489 *
14490 * ```js
14491 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14492 * // are available in the current lexical scope (they could have been injected or passed in).
14493 *
14494 * function asyncGreet(name) {
14495 * var deferred = $q.defer();
14496 *
14497 * setTimeout(function() {
14498 * deferred.notify('About to greet ' + name + '.');
14499 *
14500 * if (okToGreet(name)) {
14501 * deferred.resolve('Hello, ' + name + '!');
14502 * } else {
14503 * deferred.reject('Greeting ' + name + ' is not allowed.');
14504 * }
14505 * }, 1000);
14506 *
14507 * return deferred.promise;
14508 * }
14509 *
14510 * var promise = asyncGreet('Robin Hood');
14511 * promise.then(function(greeting) {
14512 * alert('Success: ' + greeting);
14513 * }, function(reason) {
14514 * alert('Failed: ' + reason);
14515 * }, function(update) {
14516 * alert('Got notification: ' + update);
14517 * });
14518 * ```
14519 *
14520 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14521 * comes in the way of guarantees that promise and deferred APIs make, see
14522 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14523 *
14524 * Additionally the promise api allows for composition that is very hard to do with the
14525 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14526 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14527 * section on serial or parallel joining of promises.
14528 *
14529 * # The Deferred API
14530 *
14531 * A new instance of deferred is constructed by calling `$q.defer()`.
14532 *
14533 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14534 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14535 * of the task.
14536 *
14537 * **Methods**
14538 *
14539 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14540 * constructed via `$q.reject`, the promise will be rejected instead.
14541 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14542 * resolving it with a rejection constructed via `$q.reject`.
14543 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14544 * multiple times before the promise is either resolved or rejected.
14545 *
14546 * **Properties**
14547 *
14548 * - promise – `{Promise}` – promise object associated with this deferred.
14549 *
14550 *
14551 * # The Promise API
14552 *
14553 * A new promise instance is created when a deferred instance is created and can be retrieved by
14554 * calling `deferred.promise`.
14555 *
14556 * The purpose of the promise object is to allow for interested parties to get access to the result
14557 * of the deferred task when it completes.
14558 *
14559 * **Methods**
14560 *
14561 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14562 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14563 * as soon as the result is available. The callbacks are called with a single argument: the result
14564 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14565 * provide a progress indication, before the promise is resolved or rejected.
14566 *
14567 * This method *returns a new promise* which is resolved or rejected via the return value of the
14568 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14569 * with the value which is resolved in that promise using
14570 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14571 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14572 * resolved or rejected from the notifyCallback method.
14573 *
14574 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14575 *
14576 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14577 * but to do so without modifying the final value. This is useful to release resources or do some
14578 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14579 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14580 * more information.
14581 *
14582 * # Chaining promises
14583 *
14584 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14585 * possible to create a chain of promises:
14586 *
14587 * ```js
14588 * promiseB = promiseA.then(function(result) {
14589 * return result + 1;
14590 * });
14591 *
14592 * // promiseB will be resolved immediately after promiseA is resolved and its value
14593 * // will be the result of promiseA incremented by 1
14594 * ```
14595 *
14596 * It is possible to create chains of any length and since a promise can be resolved with another
14597 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14598 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14599 * $http's response interceptors.
14600 *
14601 *
14602 * # Differences between Kris Kowal's Q and $q
14603 *
14604 * There are two main differences:
14605 *
14606 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14607 * mechanism in angular, which means faster propagation of resolution or rejection into your
14608 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14609 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14610 * all the important functionality needed for common async tasks.
14611 *
14612 * # Testing
14613 *
14614 * ```js
14615 * it('should simulate promise', inject(function($q, $rootScope) {
14616 * var deferred = $q.defer();
14617 * var promise = deferred.promise;
14618 * var resolvedValue;
14619 *
14620 * promise.then(function(value) { resolvedValue = value; });
14621 * expect(resolvedValue).toBeUndefined();
14622 *
14623 * // Simulate resolving of promise
14624 * deferred.resolve(123);
14625 * // Note that the 'then' function does not get called synchronously.
14626 * // This is because we want the promise API to always be async, whether or not
14627 * // it got called synchronously or asynchronously.
14628 * expect(resolvedValue).toBeUndefined();
14629 *
14630 * // Propagate promise resolution to 'then' functions using $apply().
14631 * $rootScope.$apply();
14632 * expect(resolvedValue).toEqual(123);
14633 * }));
14634 * ```
14635 *
14636 * @param {function(function, function)} resolver Function which is responsible for resolving or
14637 * rejecting the newly created promise. The first parameter is a function which resolves the
14638 * promise, the second parameter is a function which rejects the promise.
14639 *
14640 * @returns {Promise} The newly created promise.
14641 */
14642function $QProvider() {
14643
14644 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14645 return qFactory(function(callback) {
14646 $rootScope.$evalAsync(callback);
14647 }, $exceptionHandler);
14648 }];
14649}
14650
14651function $$QProvider() {
14652 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14653 return qFactory(function(callback) {
14654 $browser.defer(callback);
14655 }, $exceptionHandler);
14656 }];
14657}
14658
14659/**
14660 * Constructs a promise manager.
14661 *
14662 * @param {function(function)} nextTick Function for executing functions in the next turn.
14663 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14664 * debugging purposes.
14665 * @returns {object} Promise manager.
14666 */
14667function qFactory(nextTick, exceptionHandler) {
14668 var $qMinErr = minErr('$q', TypeError);
14669 function callOnce(self, resolveFn, rejectFn) {
14670 var called = false;
14671 function wrap(fn) {
14672 return function(value) {
14673 if (called) return;
14674 called = true;
14675 fn.call(self, value);
14676 };
14677 }
14678
14679 return [wrap(resolveFn), wrap(rejectFn)];
14680 }
14681
14682 /**
14683 * @ngdoc method
14684 * @name ng.$q#defer
14685 * @kind function
14686 *
14687 * @description
14688 * Creates a `Deferred` object which represents a task which will finish in the future.
14689 *
14690 * @returns {Deferred} Returns a new instance of deferred.
14691 */
14692 var defer = function() {
14693 return new Deferred();
14694 };
14695
14696 function Promise() {
14697 this.$$state = { status: 0 };
14698 }
14699
14700 extend(Promise.prototype, {
14701 then: function(onFulfilled, onRejected, progressBack) {
14702 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14703 return this;
14704 }
14705 var result = new Deferred();
14706
14707 this.$$state.pending = this.$$state.pending || [];
14708 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14709 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14710
14711 return result.promise;
14712 },
14713
14714 "catch": function(callback) {
14715 return this.then(null, callback);
14716 },
14717
14718 "finally": function(callback, progressBack) {
14719 return this.then(function(value) {
14720 return handleCallback(value, true, callback);
14721 }, function(error) {
14722 return handleCallback(error, false, callback);
14723 }, progressBack);
14724 }
14725 });
14726
14727 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14728 function simpleBind(context, fn) {
14729 return function(value) {
14730 fn.call(context, value);
14731 };
14732 }
14733
14734 function processQueue(state) {
14735 var fn, deferred, pending;
14736
14737 pending = state.pending;
14738 state.processScheduled = false;
14739 state.pending = undefined;
14740 for (var i = 0, ii = pending.length; i < ii; ++i) {
14741 deferred = pending[i][0];
14742 fn = pending[i][state.status];
14743 try {
14744 if (isFunction(fn)) {
14745 deferred.resolve(fn(state.value));
14746 } else if (state.status === 1) {
14747 deferred.resolve(state.value);
14748 } else {
14749 deferred.reject(state.value);
14750 }
14751 } catch (e) {
14752 deferred.reject(e);
14753 exceptionHandler(e);
14754 }
14755 }
14756 }
14757
14758 function scheduleProcessQueue(state) {
14759 if (state.processScheduled || !state.pending) return;
14760 state.processScheduled = true;
14761 nextTick(function() { processQueue(state); });
14762 }
14763
14764 function Deferred() {
14765 this.promise = new Promise();
14766 //Necessary to support unbound execution :/
14767 this.resolve = simpleBind(this, this.resolve);
14768 this.reject = simpleBind(this, this.reject);
14769 this.notify = simpleBind(this, this.notify);
14770 }
14771
14772 extend(Deferred.prototype, {
14773 resolve: function(val) {
14774 if (this.promise.$$state.status) return;
14775 if (val === this.promise) {
14776 this.$$reject($qMinErr(
14777 'qcycle',
14778 "Expected promise to be resolved with value other than itself '{0}'",
14779 val));
14780 } else {
14781 this.$$resolve(val);
14782 }
14783
14784 },
14785
14786 $$resolve: function(val) {
14787 var then, fns;
14788
14789 fns = callOnce(this, this.$$resolve, this.$$reject);
14790 try {
14791 if ((isObject(val) || isFunction(val))) then = val && val.then;
14792 if (isFunction(then)) {
14793 this.promise.$$state.status = -1;
14794 then.call(val, fns[0], fns[1], this.notify);
14795 } else {
14796 this.promise.$$state.value = val;
14797 this.promise.$$state.status = 1;
14798 scheduleProcessQueue(this.promise.$$state);
14799 }
14800 } catch (e) {
14801 fns[1](e);
14802 exceptionHandler(e);
14803 }
14804 },
14805
14806 reject: function(reason) {
14807 if (this.promise.$$state.status) return;
14808 this.$$reject(reason);
14809 },
14810
14811 $$reject: function(reason) {
14812 this.promise.$$state.value = reason;
14813 this.promise.$$state.status = 2;
14814 scheduleProcessQueue(this.promise.$$state);
14815 },
14816
14817 notify: function(progress) {
14818 var callbacks = this.promise.$$state.pending;
14819
14820 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14821 nextTick(function() {
14822 var callback, result;
14823 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14824 result = callbacks[i][0];
14825 callback = callbacks[i][3];
14826 try {
14827 result.notify(isFunction(callback) ? callback(progress) : progress);
14828 } catch (e) {
14829 exceptionHandler(e);
14830 }
14831 }
14832 });
14833 }
14834 }
14835 });
14836
14837 /**
14838 * @ngdoc method
14839 * @name $q#reject
14840 * @kind function
14841 *
14842 * @description
14843 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14844 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14845 * a promise chain, you don't need to worry about it.
14846 *
14847 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14848 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14849 * a promise error callback and you want to forward the error to the promise derived from the
14850 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14851 * `reject`.
14852 *
14853 * ```js
14854 * promiseB = promiseA.then(function(result) {
14855 * // success: do something and resolve promiseB
14856 * // with the old or a new result
14857 * return result;
14858 * }, function(reason) {
14859 * // error: handle the error if possible and
14860 * // resolve promiseB with newPromiseOrValue,
14861 * // otherwise forward the rejection to promiseB
14862 * if (canHandle(reason)) {
14863 * // handle the error and recover
14864 * return newPromiseOrValue;
14865 * }
14866 * return $q.reject(reason);
14867 * });
14868 * ```
14869 *
14870 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14871 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14872 */
14873 var reject = function(reason) {
14874 var result = new Deferred();
14875 result.reject(reason);
14876 return result.promise;
14877 };
14878
14879 var makePromise = function makePromise(value, resolved) {
14880 var result = new Deferred();
14881 if (resolved) {
14882 result.resolve(value);
14883 } else {
14884 result.reject(value);
14885 }
14886 return result.promise;
14887 };
14888
14889 var handleCallback = function handleCallback(value, isResolved, callback) {
14890 var callbackOutput = null;
14891 try {
14892 if (isFunction(callback)) callbackOutput = callback();
14893 } catch (e) {
14894 return makePromise(e, false);
14895 }
14896 if (isPromiseLike(callbackOutput)) {
14897 return callbackOutput.then(function() {
14898 return makePromise(value, isResolved);
14899 }, function(error) {
14900 return makePromise(error, false);
14901 });
14902 } else {
14903 return makePromise(value, isResolved);
14904 }
14905 };
14906
14907 /**
14908 * @ngdoc method
14909 * @name $q#when
14910 * @kind function
14911 *
14912 * @description
14913 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
14914 * This is useful when you are dealing with an object that might or might not be a promise, or if
14915 * the promise comes from a source that can't be trusted.
14916 *
14917 * @param {*} value Value or a promise
14918 * @param {Function=} successCallback
14919 * @param {Function=} errorCallback
14920 * @param {Function=} progressCallback
14921 * @returns {Promise} Returns a promise of the passed value or promise
14922 */
14923
14924
14925 var when = function(value, callback, errback, progressBack) {
14926 var result = new Deferred();
14927 result.resolve(value);
14928 return result.promise.then(callback, errback, progressBack);
14929 };
14930
14931 /**
14932 * @ngdoc method
14933 * @name $q#resolve
14934 * @kind function
14935 *
14936 * @description
14937 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
14938 *
14939 * @param {*} value Value or a promise
14940 * @param {Function=} successCallback
14941 * @param {Function=} errorCallback
14942 * @param {Function=} progressCallback
14943 * @returns {Promise} Returns a promise of the passed value or promise
14944 */
14945 var resolve = when;
14946
14947 /**
14948 * @ngdoc method
14949 * @name $q#all
14950 * @kind function
14951 *
14952 * @description
14953 * Combines multiple promises into a single promise that is resolved when all of the input
14954 * promises are resolved.
14955 *
14956 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
14957 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
14958 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
14959 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
14960 * with the same rejection value.
14961 */
14962
14963 function all(promises) {
14964 var deferred = new Deferred(),
14965 counter = 0,
14966 results = isArray(promises) ? [] : {};
14967
14968 forEach(promises, function(promise, key) {
14969 counter++;
14970 when(promise).then(function(value) {
14971 if (results.hasOwnProperty(key)) return;
14972 results[key] = value;
14973 if (!(--counter)) deferred.resolve(results);
14974 }, function(reason) {
14975 if (results.hasOwnProperty(key)) return;
14976 deferred.reject(reason);
14977 });
14978 });
14979
14980 if (counter === 0) {
14981 deferred.resolve(results);
14982 }
14983
14984 return deferred.promise;
14985 }
14986
14987 var $Q = function Q(resolver) {
14988 if (!isFunction(resolver)) {
14989 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
14990 }
14991
14992 if (!(this instanceof Q)) {
14993 // More useful when $Q is the Promise itself.
14994 return new Q(resolver);
14995 }
14996
14997 var deferred = new Deferred();
14998
14999 function resolveFn(value) {
15000 deferred.resolve(value);
15001 }
15002
15003 function rejectFn(reason) {
15004 deferred.reject(reason);
15005 }
15006
15007 resolver(resolveFn, rejectFn);
15008
15009 return deferred.promise;
15010 };
15011
15012 $Q.defer = defer;
15013 $Q.reject = reject;
15014 $Q.when = when;
15015 $Q.resolve = resolve;
15016 $Q.all = all;
15017
15018 return $Q;
15019}
15020
15021function $$RAFProvider() { //rAF
15022 this.$get = ['$window', '$timeout', function($window, $timeout) {
15023 var requestAnimationFrame = $window.requestAnimationFrame ||
15024 $window.webkitRequestAnimationFrame;
15025
15026 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15027 $window.webkitCancelAnimationFrame ||
15028 $window.webkitCancelRequestAnimationFrame;
15029
15030 var rafSupported = !!requestAnimationFrame;
15031 var raf = rafSupported
15032 ? function(fn) {
15033 var id = requestAnimationFrame(fn);
15034 return function() {
15035 cancelAnimationFrame(id);
15036 };
15037 }
15038 : function(fn) {
15039 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15040 return function() {
15041 $timeout.cancel(timer);
15042 };
15043 };
15044
15045 raf.supported = rafSupported;
15046
15047 return raf;
15048 }];
15049}
15050
15051/**
15052 * DESIGN NOTES
15053 *
15054 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15055 *
15056 * The typical use of scope is to watch the expressions, which most of the time return the same
15057 * value as last time so we optimize the operation.
15058 *
15059 * Closures construction is expensive in terms of speed as well as memory:
15060 * - No closures, instead use prototypical inheritance for API
15061 * - Internal state needs to be stored on scope directly, which means that private state is
15062 * exposed as $$____ properties
15063 *
15064 * Loop operations are optimized by using while(count--) { ... }
15065 * - this means that in order to keep the same order of execution as addition we have to add
15066 * items to the array at the beginning (unshift) instead of at the end (push)
15067 *
15068 * Child scopes are created and removed often
15069 * - Using an array would be slow since inserts in middle are expensive so we use linked list
15070 *
15071 * There are few watches then a lot of observers. This is why you don't want the observer to be
15072 * implemented in the same way as watch. Watch requires return of initialization function which
15073 * are expensive to construct.
15074 */
15075
15076
15077/**
15078 * @ngdoc provider
15079 * @name $rootScopeProvider
15080 * @description
15081 *
15082 * Provider for the $rootScope service.
15083 */
15084
15085/**
15086 * @ngdoc method
15087 * @name $rootScopeProvider#digestTtl
15088 * @description
15089 *
15090 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15091 * assuming that the model is unstable.
15092 *
15093 * The current default is 10 iterations.
15094 *
15095 * In complex applications it's possible that the dependencies between `$watch`s will result in
15096 * several digest iterations. However if an application needs more than the default 10 digest
15097 * iterations for its model to stabilize then you should investigate what is causing the model to
15098 * continuously change during the digest.
15099 *
15100 * Increasing the TTL could have performance implications, so you should not change it without
15101 * proper justification.
15102 *
15103 * @param {number} limit The number of digest iterations.
15104 */
15105
15106
15107/**
15108 * @ngdoc service
15109 * @name $rootScope
15110 * @description
15111 *
15112 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15113 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15114 * between the model and the view, via a mechanism for watching the model for changes.
15115 * They also provide an event emission/broadcast and subscription facility. See the
15116 * {@link guide/scope developer guide on scopes}.
15117 */
15118function $RootScopeProvider() {
15119 var TTL = 10;
15120 var $rootScopeMinErr = minErr('$rootScope');
15121 var lastDirtyWatch = null;
15122 var applyAsyncId = null;
15123
15124 this.digestTtl = function(value) {
15125 if (arguments.length) {
15126 TTL = value;
15127 }
15128 return TTL;
15129 };
15130
15131 function createChildScopeClass(parent) {
15132 function ChildScope() {
15133 this.$$watchers = this.$$nextSibling =
15134 this.$$childHead = this.$$childTail = null;
15135 this.$$listeners = {};
15136 this.$$listenerCount = {};
15137 this.$$watchersCount = 0;
15138 this.$id = nextUid();
15139 this.$$ChildScope = null;
15140 }
15141 ChildScope.prototype = parent;
15142 return ChildScope;
15143 }
15144
15145 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15146 function($injector, $exceptionHandler, $parse, $browser) {
15147
15148 function destroyChildScope($event) {
15149 $event.currentScope.$$destroyed = true;
15150 }
15151
15152 /**
15153 * @ngdoc type
15154 * @name $rootScope.Scope
15155 *
15156 * @description
15157 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15158 * {@link auto.$injector $injector}. Child scopes are created using the
15159 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15160 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15161 * an in-depth introduction and usage examples.
15162 *
15163 *
15164 * # Inheritance
15165 * A scope can inherit from a parent scope, as in this example:
15166 * ```js
15167 var parent = $rootScope;
15168 var child = parent.$new();
15169
15170 parent.salutation = "Hello";
15171 expect(child.salutation).toEqual('Hello');
15172
15173 child.salutation = "Welcome";
15174 expect(child.salutation).toEqual('Welcome');
15175 expect(parent.salutation).toEqual('Hello');
15176 * ```
15177 *
15178 * When interacting with `Scope` in tests, additional helper methods are available on the
15179 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15180 * details.
15181 *
15182 *
15183 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15184 * provided for the current scope. Defaults to {@link ng}.
15185 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15186 * append/override services provided by `providers`. This is handy
15187 * when unit-testing and having the need to override a default
15188 * service.
15189 * @returns {Object} Newly created scope.
15190 *
15191 */
15192 function Scope() {
15193 this.$id = nextUid();
15194 this.$$phase = this.$parent = this.$$watchers =
15195 this.$$nextSibling = this.$$prevSibling =
15196 this.$$childHead = this.$$childTail = null;
15197 this.$root = this;
15198 this.$$destroyed = false;
15199 this.$$listeners = {};
15200 this.$$listenerCount = {};
15201 this.$$watchersCount = 0;
15202 this.$$isolateBindings = null;
15203 }
15204
15205 /**
15206 * @ngdoc property
15207 * @name $rootScope.Scope#$id
15208 *
15209 * @description
15210 * Unique scope ID (monotonically increasing) useful for debugging.
15211 */
15212
15213 /**
15214 * @ngdoc property
15215 * @name $rootScope.Scope#$parent
15216 *
15217 * @description
15218 * Reference to the parent scope.
15219 */
15220
15221 /**
15222 * @ngdoc property
15223 * @name $rootScope.Scope#$root
15224 *
15225 * @description
15226 * Reference to the root scope.
15227 */
15228
15229 Scope.prototype = {
15230 constructor: Scope,
15231 /**
15232 * @ngdoc method
15233 * @name $rootScope.Scope#$new
15234 * @kind function
15235 *
15236 * @description
15237 * Creates a new child {@link ng.$rootScope.Scope scope}.
15238 *
15239 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15240 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15241 *
15242 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15243 * desired for the scope and its child scopes to be permanently detached from the parent and
15244 * thus stop participating in model change detection and listener notification by invoking.
15245 *
15246 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15247 * parent scope. The scope is isolated, as it can not see parent scope properties.
15248 * When creating widgets, it is useful for the widget to not accidentally read parent
15249 * state.
15250 *
15251 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15252 * of the newly created scope. Defaults to `this` scope if not provided.
15253 * This is used when creating a transclude scope to correctly place it
15254 * in the scope hierarchy while maintaining the correct prototypical
15255 * inheritance.
15256 *
15257 * @returns {Object} The newly created child scope.
15258 *
15259 */
15260 $new: function(isolate, parent) {
15261 var child;
15262
15263 parent = parent || this;
15264
15265 if (isolate) {
15266 child = new Scope();
15267 child.$root = this.$root;
15268 } else {
15269 // Only create a child scope class if somebody asks for one,
15270 // but cache it to allow the VM to optimize lookups.
15271 if (!this.$$ChildScope) {
15272 this.$$ChildScope = createChildScopeClass(this);
15273 }
15274 child = new this.$$ChildScope();
15275 }
15276 child.$parent = parent;
15277 child.$$prevSibling = parent.$$childTail;
15278 if (parent.$$childHead) {
15279 parent.$$childTail.$$nextSibling = child;
15280 parent.$$childTail = child;
15281 } else {
15282 parent.$$childHead = parent.$$childTail = child;
15283 }
15284
15285 // When the new scope is not isolated or we inherit from `this`, and
15286 // the parent scope is destroyed, the property `$$destroyed` is inherited
15287 // prototypically. In all other cases, this property needs to be set
15288 // when the parent scope is destroyed.
15289 // The listener needs to be added after the parent is set
15290 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15291
15292 return child;
15293 },
15294
15295 /**
15296 * @ngdoc method
15297 * @name $rootScope.Scope#$watch
15298 * @kind function
15299 *
15300 * @description
15301 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15302 *
15303 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15304 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15305 * its value when executed multiple times with the same input because it may be executed multiple
15306 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15307 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15308 * - The `listener` is called only when the value from the current `watchExpression` and the
15309 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15310 * see below). Inequality is determined according to reference inequality,
15311 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15312 * via the `!==` Javascript operator, unless `objectEquality == true`
15313 * (see next point)
15314 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15315 * according to the {@link angular.equals} function. To save the value of the object for
15316 * later comparison, the {@link angular.copy} function is used. This therefore means that
15317 * watching complex objects will have adverse memory and performance implications.
15318 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15319 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15320 * iteration limit is 10 to prevent an infinite loop deadlock.
15321 *
15322 *
15323 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15324 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15325 * multiple calls to your `watchExpression` because it will execute multiple times in a
15326 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15327 *
15328 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15329 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15330 * watcher. In rare cases, this is undesirable because the listener is called when the result
15331 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15332 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15333 * listener was called due to initialization.
15334 *
15335 *
15336 *
15337 * # Example
15338 * ```js
15339 // let's assume that scope was dependency injected as the $rootScope
15340 var scope = $rootScope;
15341 scope.name = 'misko';
15342 scope.counter = 0;
15343
15344 expect(scope.counter).toEqual(0);
15345 scope.$watch('name', function(newValue, oldValue) {
15346 scope.counter = scope.counter + 1;
15347 });
15348 expect(scope.counter).toEqual(0);
15349
15350 scope.$digest();
15351 // the listener is always called during the first $digest loop after it was registered
15352 expect(scope.counter).toEqual(1);
15353
15354 scope.$digest();
15355 // but now it will not be called unless the value changes
15356 expect(scope.counter).toEqual(1);
15357
15358 scope.name = 'adam';
15359 scope.$digest();
15360 expect(scope.counter).toEqual(2);
15361
15362
15363
15364 // Using a function as a watchExpression
15365 var food;
15366 scope.foodCounter = 0;
15367 expect(scope.foodCounter).toEqual(0);
15368 scope.$watch(
15369 // This function returns the value being watched. It is called for each turn of the $digest loop
15370 function() { return food; },
15371 // This is the change listener, called when the value returned from the above function changes
15372 function(newValue, oldValue) {
15373 if ( newValue !== oldValue ) {
15374 // Only increment the counter if the value changed
15375 scope.foodCounter = scope.foodCounter + 1;
15376 }
15377 }
15378 );
15379 // No digest has been run so the counter will be zero
15380 expect(scope.foodCounter).toEqual(0);
15381
15382 // Run the digest but since food has not changed count will still be zero
15383 scope.$digest();
15384 expect(scope.foodCounter).toEqual(0);
15385
15386 // Update food and run digest. Now the counter will increment
15387 food = 'cheeseburger';
15388 scope.$digest();
15389 expect(scope.foodCounter).toEqual(1);
15390
15391 * ```
15392 *
15393 *
15394 *
15395 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15396 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15397 * a call to the `listener`.
15398 *
15399 * - `string`: Evaluated as {@link guide/expression expression}
15400 * - `function(scope)`: called with current `scope` as a parameter.
15401 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15402 * of `watchExpression` changes.
15403 *
15404 * - `newVal` contains the current value of the `watchExpression`
15405 * - `oldVal` contains the previous value of the `watchExpression`
15406 * - `scope` refers to the current scope
15407 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15408 * comparing for reference equality.
15409 * @returns {function()} Returns a deregistration function for this listener.
15410 */
15411 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15412 var get = $parse(watchExp);
15413
15414 if (get.$$watchDelegate) {
15415 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15416 }
15417 var scope = this,
15418 array = scope.$$watchers,
15419 watcher = {
15420 fn: listener,
15421 last: initWatchVal,
15422 get: get,
15423 exp: prettyPrintExpression || watchExp,
15424 eq: !!objectEquality
15425 };
15426
15427 lastDirtyWatch = null;
15428
15429 if (!isFunction(listener)) {
15430 watcher.fn = noop;
15431 }
15432
15433 if (!array) {
15434 array = scope.$$watchers = [];
15435 }
15436 // we use unshift since we use a while loop in $digest for speed.
15437 // the while loop reads in reverse order.
15438 array.unshift(watcher);
15439 incrementWatchersCount(this, 1);
15440
15441 return function deregisterWatch() {
15442 if (arrayRemove(array, watcher) >= 0) {
15443 incrementWatchersCount(scope, -1);
15444 }
15445 lastDirtyWatch = null;
15446 };
15447 },
15448
15449 /**
15450 * @ngdoc method
15451 * @name $rootScope.Scope#$watchGroup
15452 * @kind function
15453 *
15454 * @description
15455 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15456 * If any one expression in the collection changes the `listener` is executed.
15457 *
15458 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15459 * call to $digest() to see if any items changes.
15460 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15461 *
15462 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15463 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15464 *
15465 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15466 * expression in `watchExpressions` changes
15467 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15468 * those of `watchExpression`
15469 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15470 * those of `watchExpression`
15471 * The `scope` refers to the current scope.
15472 * @returns {function()} Returns a de-registration function for all listeners.
15473 */
15474 $watchGroup: function(watchExpressions, listener) {
15475 var oldValues = new Array(watchExpressions.length);
15476 var newValues = new Array(watchExpressions.length);
15477 var deregisterFns = [];
15478 var self = this;
15479 var changeReactionScheduled = false;
15480 var firstRun = true;
15481
15482 if (!watchExpressions.length) {
15483 // No expressions means we call the listener ASAP
15484 var shouldCall = true;
15485 self.$evalAsync(function() {
15486 if (shouldCall) listener(newValues, newValues, self);
15487 });
15488 return function deregisterWatchGroup() {
15489 shouldCall = false;
15490 };
15491 }
15492
15493 if (watchExpressions.length === 1) {
15494 // Special case size of one
15495 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15496 newValues[0] = value;
15497 oldValues[0] = oldValue;
15498 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15499 });
15500 }
15501
15502 forEach(watchExpressions, function(expr, i) {
15503 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15504 newValues[i] = value;
15505 oldValues[i] = oldValue;
15506 if (!changeReactionScheduled) {
15507 changeReactionScheduled = true;
15508 self.$evalAsync(watchGroupAction);
15509 }
15510 });
15511 deregisterFns.push(unwatchFn);
15512 });
15513
15514 function watchGroupAction() {
15515 changeReactionScheduled = false;
15516
15517 if (firstRun) {
15518 firstRun = false;
15519 listener(newValues, newValues, self);
15520 } else {
15521 listener(newValues, oldValues, self);
15522 }
15523 }
15524
15525 return function deregisterWatchGroup() {
15526 while (deregisterFns.length) {
15527 deregisterFns.shift()();
15528 }
15529 };
15530 },
15531
15532
15533 /**
15534 * @ngdoc method
15535 * @name $rootScope.Scope#$watchCollection
15536 * @kind function
15537 *
15538 * @description
15539 * Shallow watches the properties of an object and fires whenever any of the properties change
15540 * (for arrays, this implies watching the array items; for object maps, this implies watching
15541 * the properties). If a change is detected, the `listener` callback is fired.
15542 *
15543 * - The `obj` collection is observed via standard $watch operation and is examined on every
15544 * call to $digest() to see if any items have been added, removed, or moved.
15545 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15546 * adding, removing, and moving items belonging to an object or array.
15547 *
15548 *
15549 * # Example
15550 * ```js
15551 $scope.names = ['igor', 'matias', 'misko', 'james'];
15552 $scope.dataCount = 4;
15553
15554 $scope.$watchCollection('names', function(newNames, oldNames) {
15555 $scope.dataCount = newNames.length;
15556 });
15557
15558 expect($scope.dataCount).toEqual(4);
15559 $scope.$digest();
15560
15561 //still at 4 ... no changes
15562 expect($scope.dataCount).toEqual(4);
15563
15564 $scope.names.pop();
15565 $scope.$digest();
15566
15567 //now there's been a change
15568 expect($scope.dataCount).toEqual(3);
15569 * ```
15570 *
15571 *
15572 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15573 * expression value should evaluate to an object or an array which is observed on each
15574 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15575 * collection will trigger a call to the `listener`.
15576 *
15577 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15578 * when a change is detected.
15579 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15580 * - The `oldCollection` object is a copy of the former collection data.
15581 * Due to performance considerations, the`oldCollection` value is computed only if the
15582 * `listener` function declares two or more arguments.
15583 * - The `scope` argument refers to the current scope.
15584 *
15585 * @returns {function()} Returns a de-registration function for this listener. When the
15586 * de-registration function is executed, the internal watch operation is terminated.
15587 */
15588 $watchCollection: function(obj, listener) {
15589 $watchCollectionInterceptor.$stateful = true;
15590
15591 var self = this;
15592 // the current value, updated on each dirty-check run
15593 var newValue;
15594 // a shallow copy of the newValue from the last dirty-check run,
15595 // updated to match newValue during dirty-check run
15596 var oldValue;
15597 // a shallow copy of the newValue from when the last change happened
15598 var veryOldValue;
15599 // only track veryOldValue if the listener is asking for it
15600 var trackVeryOldValue = (listener.length > 1);
15601 var changeDetected = 0;
15602 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15603 var internalArray = [];
15604 var internalObject = {};
15605 var initRun = true;
15606 var oldLength = 0;
15607
15608 function $watchCollectionInterceptor(_value) {
15609 newValue = _value;
15610 var newLength, key, bothNaN, newItem, oldItem;
15611
15612 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15613 if (isUndefined(newValue)) return;
15614
15615 if (!isObject(newValue)) { // if primitive
15616 if (oldValue !== newValue) {
15617 oldValue = newValue;
15618 changeDetected++;
15619 }
15620 } else if (isArrayLike(newValue)) {
15621 if (oldValue !== internalArray) {
15622 // we are transitioning from something which was not an array into array.
15623 oldValue = internalArray;
15624 oldLength = oldValue.length = 0;
15625 changeDetected++;
15626 }
15627
15628 newLength = newValue.length;
15629
15630 if (oldLength !== newLength) {
15631 // if lengths do not match we need to trigger change notification
15632 changeDetected++;
15633 oldValue.length = oldLength = newLength;
15634 }
15635 // copy the items to oldValue and look for changes.
15636 for (var i = 0; i < newLength; i++) {
15637 oldItem = oldValue[i];
15638 newItem = newValue[i];
15639
15640 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15641 if (!bothNaN && (oldItem !== newItem)) {
15642 changeDetected++;
15643 oldValue[i] = newItem;
15644 }
15645 }
15646 } else {
15647 if (oldValue !== internalObject) {
15648 // we are transitioning from something which was not an object into object.
15649 oldValue = internalObject = {};
15650 oldLength = 0;
15651 changeDetected++;
15652 }
15653 // copy the items to oldValue and look for changes.
15654 newLength = 0;
15655 for (key in newValue) {
15656 if (hasOwnProperty.call(newValue, key)) {
15657 newLength++;
15658 newItem = newValue[key];
15659 oldItem = oldValue[key];
15660
15661 if (key in oldValue) {
15662 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15663 if (!bothNaN && (oldItem !== newItem)) {
15664 changeDetected++;
15665 oldValue[key] = newItem;
15666 }
15667 } else {
15668 oldLength++;
15669 oldValue[key] = newItem;
15670 changeDetected++;
15671 }
15672 }
15673 }
15674 if (oldLength > newLength) {
15675 // we used to have more keys, need to find them and destroy them.
15676 changeDetected++;
15677 for (key in oldValue) {
15678 if (!hasOwnProperty.call(newValue, key)) {
15679 oldLength--;
15680 delete oldValue[key];
15681 }
15682 }
15683 }
15684 }
15685 return changeDetected;
15686 }
15687
15688 function $watchCollectionAction() {
15689 if (initRun) {
15690 initRun = false;
15691 listener(newValue, newValue, self);
15692 } else {
15693 listener(newValue, veryOldValue, self);
15694 }
15695
15696 // make a copy for the next time a collection is changed
15697 if (trackVeryOldValue) {
15698 if (!isObject(newValue)) {
15699 //primitive
15700 veryOldValue = newValue;
15701 } else if (isArrayLike(newValue)) {
15702 veryOldValue = new Array(newValue.length);
15703 for (var i = 0; i < newValue.length; i++) {
15704 veryOldValue[i] = newValue[i];
15705 }
15706 } else { // if object
15707 veryOldValue = {};
15708 for (var key in newValue) {
15709 if (hasOwnProperty.call(newValue, key)) {
15710 veryOldValue[key] = newValue[key];
15711 }
15712 }
15713 }
15714 }
15715 }
15716
15717 return this.$watch(changeDetector, $watchCollectionAction);
15718 },
15719
15720 /**
15721 * @ngdoc method
15722 * @name $rootScope.Scope#$digest
15723 * @kind function
15724 *
15725 * @description
15726 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15727 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15728 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15729 * until no more listeners are firing. This means that it is possible to get into an infinite
15730 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15731 * iterations exceeds 10.
15732 *
15733 * Usually, you don't call `$digest()` directly in
15734 * {@link ng.directive:ngController controllers} or in
15735 * {@link ng.$compileProvider#directive directives}.
15736 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15737 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15738 *
15739 * If you want to be notified whenever `$digest()` is called,
15740 * you can register a `watchExpression` function with
15741 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15742 *
15743 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15744 *
15745 * # Example
15746 * ```js
15747 var scope = ...;
15748 scope.name = 'misko';
15749 scope.counter = 0;
15750
15751 expect(scope.counter).toEqual(0);
15752 scope.$watch('name', function(newValue, oldValue) {
15753 scope.counter = scope.counter + 1;
15754 });
15755 expect(scope.counter).toEqual(0);
15756
15757 scope.$digest();
15758 // the listener is always called during the first $digest loop after it was registered
15759 expect(scope.counter).toEqual(1);
15760
15761 scope.$digest();
15762 // but now it will not be called unless the value changes
15763 expect(scope.counter).toEqual(1);
15764
15765 scope.name = 'adam';
15766 scope.$digest();
15767 expect(scope.counter).toEqual(2);
15768 * ```
15769 *
15770 */
15771 $digest: function() {
15772 var watch, value, last,
15773 watchers,
15774 length,
15775 dirty, ttl = TTL,
15776 next, current, target = this,
15777 watchLog = [],
15778 logIdx, logMsg, asyncTask;
15779
15780 beginPhase('$digest');
15781 // Check for changes to browser url that happened in sync before the call to $digest
15782 $browser.$$checkUrlChange();
15783
15784 if (this === $rootScope && applyAsyncId !== null) {
15785 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15786 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15787 $browser.defer.cancel(applyAsyncId);
15788 flushApplyAsync();
15789 }
15790
15791 lastDirtyWatch = null;
15792
15793 do { // "while dirty" loop
15794 dirty = false;
15795 current = target;
15796
15797 while (asyncQueue.length) {
15798 try {
15799 asyncTask = asyncQueue.shift();
15800 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15801 } catch (e) {
15802 $exceptionHandler(e);
15803 }
15804 lastDirtyWatch = null;
15805 }
15806
15807 traverseScopesLoop:
15808 do { // "traverse the scopes" loop
15809 if ((watchers = current.$$watchers)) {
15810 // process our watches
15811 length = watchers.length;
15812 while (length--) {
15813 try {
15814 watch = watchers[length];
15815 // Most common watches are on primitives, in which case we can short
15816 // circuit it with === operator, only when === fails do we use .equals
15817 if (watch) {
15818 if ((value = watch.get(current)) !== (last = watch.last) &&
15819 !(watch.eq
15820 ? equals(value, last)
15821 : (typeof value === 'number' && typeof last === 'number'
15822 && isNaN(value) && isNaN(last)))) {
15823 dirty = true;
15824 lastDirtyWatch = watch;
15825 watch.last = watch.eq ? copy(value, null) : value;
15826 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15827 if (ttl < 5) {
15828 logIdx = 4 - ttl;
15829 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15830 watchLog[logIdx].push({
15831 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15832 newVal: value,
15833 oldVal: last
15834 });
15835 }
15836 } else if (watch === lastDirtyWatch) {
15837 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15838 // have already been tested.
15839 dirty = false;
15840 break traverseScopesLoop;
15841 }
15842 }
15843 } catch (e) {
15844 $exceptionHandler(e);
15845 }
15846 }
15847 }
15848
15849 // Insanity Warning: scope depth-first traversal
15850 // yes, this code is a bit crazy, but it works and we have tests to prove it!
15851 // this piece should be kept in sync with the traversal in $broadcast
15852 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
15853 (current !== target && current.$$nextSibling)))) {
15854 while (current !== target && !(next = current.$$nextSibling)) {
15855 current = current.$parent;
15856 }
15857 }
15858 } while ((current = next));
15859
15860 // `break traverseScopesLoop;` takes us to here
15861
15862 if ((dirty || asyncQueue.length) && !(ttl--)) {
15863 clearPhase();
15864 throw $rootScopeMinErr('infdig',
15865 '{0} $digest() iterations reached. Aborting!\n' +
15866 'Watchers fired in the last 5 iterations: {1}',
15867 TTL, watchLog);
15868 }
15869
15870 } while (dirty || asyncQueue.length);
15871
15872 clearPhase();
15873
15874 while (postDigestQueue.length) {
15875 try {
15876 postDigestQueue.shift()();
15877 } catch (e) {
15878 $exceptionHandler(e);
15879 }
15880 }
15881 },
15882
15883
15884 /**
15885 * @ngdoc event
15886 * @name $rootScope.Scope#$destroy
15887 * @eventType broadcast on scope being destroyed
15888 *
15889 * @description
15890 * Broadcasted when a scope and its children are being destroyed.
15891 *
15892 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
15893 * clean up DOM bindings before an element is removed from the DOM.
15894 */
15895
15896 /**
15897 * @ngdoc method
15898 * @name $rootScope.Scope#$destroy
15899 * @kind function
15900 *
15901 * @description
15902 * Removes the current scope (and all of its children) from the parent scope. Removal implies
15903 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
15904 * propagate to the current scope and its children. Removal also implies that the current
15905 * scope is eligible for garbage collection.
15906 *
15907 * The `$destroy()` is usually used by directives such as
15908 * {@link ng.directive:ngRepeat ngRepeat} for managing the
15909 * unrolling of the loop.
15910 *
15911 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
15912 * Application code can register a `$destroy` event handler that will give it a chance to
15913 * perform any necessary cleanup.
15914 *
15915 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
15916 * clean up DOM bindings before an element is removed from the DOM.
15917 */
15918 $destroy: function() {
15919 // We can't destroy a scope that has been already destroyed.
15920 if (this.$$destroyed) return;
15921 var parent = this.$parent;
15922
15923 this.$broadcast('$destroy');
15924 this.$$destroyed = true;
15925
15926 if (this === $rootScope) {
15927 //Remove handlers attached to window when $rootScope is removed
15928 $browser.$$applicationDestroyed();
15929 }
15930
15931 incrementWatchersCount(this, -this.$$watchersCount);
15932 for (var eventName in this.$$listenerCount) {
15933 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
15934 }
15935
15936 // sever all the references to parent scopes (after this cleanup, the current scope should
15937 // not be retained by any of our references and should be eligible for garbage collection)
15938 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
15939 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
15940 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
15941 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
15942
15943 // Disable listeners, watchers and apply/digest methods
15944 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
15945 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
15946 this.$$listeners = {};
15947
15948 // All of the code below is bogus code that works around V8's memory leak via optimized code
15949 // and inline caches.
15950 //
15951 // see:
15952 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15953 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15954 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15955
15956 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
15957 this.$$childTail = this.$root = this.$$watchers = null;
15958 },
15959
15960 /**
15961 * @ngdoc method
15962 * @name $rootScope.Scope#$eval
15963 * @kind function
15964 *
15965 * @description
15966 * Executes the `expression` on the current scope and returns the result. Any exceptions in
15967 * the expression are propagated (uncaught). This is useful when evaluating Angular
15968 * expressions.
15969 *
15970 * # Example
15971 * ```js
15972 var scope = ng.$rootScope.Scope();
15973 scope.a = 1;
15974 scope.b = 2;
15975
15976 expect(scope.$eval('a+b')).toEqual(3);
15977 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
15978 * ```
15979 *
15980 * @param {(string|function())=} expression An angular expression to be executed.
15981 *
15982 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
15983 * - `function(scope)`: execute the function with the current `scope` parameter.
15984 *
15985 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
15986 * @returns {*} The result of evaluating the expression.
15987 */
15988 $eval: function(expr, locals) {
15989 return $parse(expr)(this, locals);
15990 },
15991
15992 /**
15993 * @ngdoc method
15994 * @name $rootScope.Scope#$evalAsync
15995 * @kind function
15996 *
15997 * @description
15998 * Executes the expression on the current scope at a later point in time.
15999 *
16000 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16001 * that:
16002 *
16003 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16004 * rendering).
16005 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16006 * `expression` execution.
16007 *
16008 * Any exceptions from the execution of the expression are forwarded to the
16009 * {@link ng.$exceptionHandler $exceptionHandler} service.
16010 *
16011 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16012 * will be scheduled. However, it is encouraged to always call code that changes the model
16013 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16014 *
16015 * @param {(string|function())=} expression An angular expression to be executed.
16016 *
16017 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16018 * - `function(scope)`: execute the function with the current `scope` parameter.
16019 *
16020 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16021 */
16022 $evalAsync: function(expr, locals) {
16023 // if we are outside of an $digest loop and this is the first time we are scheduling async
16024 // task also schedule async auto-flush
16025 if (!$rootScope.$$phase && !asyncQueue.length) {
16026 $browser.defer(function() {
16027 if (asyncQueue.length) {
16028 $rootScope.$digest();
16029 }
16030 });
16031 }
16032
16033 asyncQueue.push({scope: this, expression: expr, locals: locals});
16034 },
16035
16036 $$postDigest: function(fn) {
16037 postDigestQueue.push(fn);
16038 },
16039
16040 /**
16041 * @ngdoc method
16042 * @name $rootScope.Scope#$apply
16043 * @kind function
16044 *
16045 * @description
16046 * `$apply()` is used to execute an expression in angular from outside of the angular
16047 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16048 * Because we are calling into the angular framework we need to perform proper scope life
16049 * cycle of {@link ng.$exceptionHandler exception handling},
16050 * {@link ng.$rootScope.Scope#$digest executing watches}.
16051 *
16052 * ## Life cycle
16053 *
16054 * # Pseudo-Code of `$apply()`
16055 * ```js
16056 function $apply(expr) {
16057 try {
16058 return $eval(expr);
16059 } catch (e) {
16060 $exceptionHandler(e);
16061 } finally {
16062 $root.$digest();
16063 }
16064 }
16065 * ```
16066 *
16067 *
16068 * Scope's `$apply()` method transitions through the following stages:
16069 *
16070 * 1. The {@link guide/expression expression} is executed using the
16071 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16072 * 2. Any exceptions from the execution of the expression are forwarded to the
16073 * {@link ng.$exceptionHandler $exceptionHandler} service.
16074 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16075 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16076 *
16077 *
16078 * @param {(string|function())=} exp 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 current `scope` parameter.
16082 *
16083 * @returns {*} The result of evaluating the expression.
16084 */
16085 $apply: function(expr) {
16086 try {
16087 beginPhase('$apply');
16088 try {
16089 return this.$eval(expr);
16090 } finally {
16091 clearPhase();
16092 }
16093 } catch (e) {
16094 $exceptionHandler(e);
16095 } finally {
16096 try {
16097 $rootScope.$digest();
16098 } catch (e) {
16099 $exceptionHandler(e);
16100 throw e;
16101 }
16102 }
16103 },
16104
16105 /**
16106 * @ngdoc method
16107 * @name $rootScope.Scope#$applyAsync
16108 * @kind function
16109 *
16110 * @description
16111 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16112 * varies across browsers, but is typically around ~10 milliseconds.
16113 *
16114 * This can be used to queue up multiple expressions which need to be evaluated in the same
16115 * digest.
16116 *
16117 * @param {(string|function())=} exp An angular expression to be executed.
16118 *
16119 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16120 * - `function(scope)`: execute the function with current `scope` parameter.
16121 */
16122 $applyAsync: function(expr) {
16123 var scope = this;
16124 expr && applyAsyncQueue.push($applyAsyncExpression);
16125 scheduleApplyAsync();
16126
16127 function $applyAsyncExpression() {
16128 scope.$eval(expr);
16129 }
16130 },
16131
16132 /**
16133 * @ngdoc method
16134 * @name $rootScope.Scope#$on
16135 * @kind function
16136 *
16137 * @description
16138 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16139 * discussion of event life cycle.
16140 *
16141 * The event listener function format is: `function(event, args...)`. The `event` object
16142 * passed into the listener has the following attributes:
16143 *
16144 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16145 * `$broadcast`-ed.
16146 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16147 * event propagates through the scope hierarchy, this property is set to null.
16148 * - `name` - `{string}`: name of the event.
16149 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16150 * further event propagation (available only for events that were `$emit`-ed).
16151 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16152 * to true.
16153 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16154 *
16155 * @param {string} name Event name to listen on.
16156 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16157 * @returns {function()} Returns a deregistration function for this listener.
16158 */
16159 $on: function(name, listener) {
16160 var namedListeners = this.$$listeners[name];
16161 if (!namedListeners) {
16162 this.$$listeners[name] = namedListeners = [];
16163 }
16164 namedListeners.push(listener);
16165
16166 var current = this;
16167 do {
16168 if (!current.$$listenerCount[name]) {
16169 current.$$listenerCount[name] = 0;
16170 }
16171 current.$$listenerCount[name]++;
16172 } while ((current = current.$parent));
16173
16174 var self = this;
16175 return function() {
16176 var indexOfListener = namedListeners.indexOf(listener);
16177 if (indexOfListener !== -1) {
16178 namedListeners[indexOfListener] = null;
16179 decrementListenerCount(self, 1, name);
16180 }
16181 };
16182 },
16183
16184
16185 /**
16186 * @ngdoc method
16187 * @name $rootScope.Scope#$emit
16188 * @kind function
16189 *
16190 * @description
16191 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16192 * registered {@link ng.$rootScope.Scope#$on} listeners.
16193 *
16194 * The event life cycle starts at the scope on which `$emit` was called. All
16195 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16196 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16197 * registered listeners along the way. The event will stop propagating if one of the listeners
16198 * cancels it.
16199 *
16200 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16201 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16202 *
16203 * @param {string} name Event name to emit.
16204 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16205 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16206 */
16207 $emit: function(name, args) {
16208 var empty = [],
16209 namedListeners,
16210 scope = this,
16211 stopPropagation = false,
16212 event = {
16213 name: name,
16214 targetScope: scope,
16215 stopPropagation: function() {stopPropagation = true;},
16216 preventDefault: function() {
16217 event.defaultPrevented = true;
16218 },
16219 defaultPrevented: false
16220 },
16221 listenerArgs = concat([event], arguments, 1),
16222 i, length;
16223
16224 do {
16225 namedListeners = scope.$$listeners[name] || empty;
16226 event.currentScope = scope;
16227 for (i = 0, length = namedListeners.length; i < length; i++) {
16228
16229 // if listeners were deregistered, defragment the array
16230 if (!namedListeners[i]) {
16231 namedListeners.splice(i, 1);
16232 i--;
16233 length--;
16234 continue;
16235 }
16236 try {
16237 //allow all listeners attached to the current scope to run
16238 namedListeners[i].apply(null, listenerArgs);
16239 } catch (e) {
16240 $exceptionHandler(e);
16241 }
16242 }
16243 //if any listener on the current scope stops propagation, prevent bubbling
16244 if (stopPropagation) {
16245 event.currentScope = null;
16246 return event;
16247 }
16248 //traverse upwards
16249 scope = scope.$parent;
16250 } while (scope);
16251
16252 event.currentScope = null;
16253
16254 return event;
16255 },
16256
16257
16258 /**
16259 * @ngdoc method
16260 * @name $rootScope.Scope#$broadcast
16261 * @kind function
16262 *
16263 * @description
16264 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16265 * registered {@link ng.$rootScope.Scope#$on} listeners.
16266 *
16267 * The event life cycle starts at the scope on which `$broadcast` was called. All
16268 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16269 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16270 * scope and calls all registered listeners along the way. The event cannot be canceled.
16271 *
16272 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16273 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16274 *
16275 * @param {string} name Event name to broadcast.
16276 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16277 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16278 */
16279 $broadcast: function(name, args) {
16280 var target = this,
16281 current = target,
16282 next = target,
16283 event = {
16284 name: name,
16285 targetScope: target,
16286 preventDefault: function() {
16287 event.defaultPrevented = true;
16288 },
16289 defaultPrevented: false
16290 };
16291
16292 if (!target.$$listenerCount[name]) return event;
16293
16294 var listenerArgs = concat([event], arguments, 1),
16295 listeners, i, length;
16296
16297 //down while you can, then up and next sibling or up and next sibling until back at root
16298 while ((current = next)) {
16299 event.currentScope = current;
16300 listeners = current.$$listeners[name] || [];
16301 for (i = 0, length = listeners.length; i < length; i++) {
16302 // if listeners were deregistered, defragment the array
16303 if (!listeners[i]) {
16304 listeners.splice(i, 1);
16305 i--;
16306 length--;
16307 continue;
16308 }
16309
16310 try {
16311 listeners[i].apply(null, listenerArgs);
16312 } catch (e) {
16313 $exceptionHandler(e);
16314 }
16315 }
16316
16317 // Insanity Warning: scope depth-first traversal
16318 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16319 // this piece should be kept in sync with the traversal in $digest
16320 // (though it differs due to having the extra check for $$listenerCount)
16321 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16322 (current !== target && current.$$nextSibling)))) {
16323 while (current !== target && !(next = current.$$nextSibling)) {
16324 current = current.$parent;
16325 }
16326 }
16327 }
16328
16329 event.currentScope = null;
16330 return event;
16331 }
16332 };
16333
16334 var $rootScope = new Scope();
16335
16336 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16337 var asyncQueue = $rootScope.$$asyncQueue = [];
16338 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16339 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16340
16341 return $rootScope;
16342
16343
16344 function beginPhase(phase) {
16345 if ($rootScope.$$phase) {
16346 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16347 }
16348
16349 $rootScope.$$phase = phase;
16350 }
16351
16352 function clearPhase() {
16353 $rootScope.$$phase = null;
16354 }
16355
16356 function incrementWatchersCount(current, count) {
16357 do {
16358 current.$$watchersCount += count;
16359 } while ((current = current.$parent));
16360 }
16361
16362 function decrementListenerCount(current, count, name) {
16363 do {
16364 current.$$listenerCount[name] -= count;
16365
16366 if (current.$$listenerCount[name] === 0) {
16367 delete current.$$listenerCount[name];
16368 }
16369 } while ((current = current.$parent));
16370 }
16371
16372 /**
16373 * function used as an initial value for watchers.
16374 * because it's unique we can easily tell it apart from other values
16375 */
16376 function initWatchVal() {}
16377
16378 function flushApplyAsync() {
16379 while (applyAsyncQueue.length) {
16380 try {
16381 applyAsyncQueue.shift()();
16382 } catch (e) {
16383 $exceptionHandler(e);
16384 }
16385 }
16386 applyAsyncId = null;
16387 }
16388
16389 function scheduleApplyAsync() {
16390 if (applyAsyncId === null) {
16391 applyAsyncId = $browser.defer(function() {
16392 $rootScope.$apply(flushApplyAsync);
16393 });
16394 }
16395 }
16396 }];
16397}
16398
16399/**
16400 * @description
16401 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16402 */
16403function $$SanitizeUriProvider() {
16404 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16405 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16406
16407 /**
16408 * @description
16409 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16410 * urls during a[href] sanitization.
16411 *
16412 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16413 *
16414 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16415 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16416 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16417 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16418 *
16419 * @param {RegExp=} regexp New regexp to whitelist urls with.
16420 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16421 * chaining otherwise.
16422 */
16423 this.aHrefSanitizationWhitelist = function(regexp) {
16424 if (isDefined(regexp)) {
16425 aHrefSanitizationWhitelist = regexp;
16426 return this;
16427 }
16428 return aHrefSanitizationWhitelist;
16429 };
16430
16431
16432 /**
16433 * @description
16434 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16435 * urls during img[src] sanitization.
16436 *
16437 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16438 *
16439 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16440 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16441 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16442 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16443 *
16444 * @param {RegExp=} regexp New regexp to whitelist urls with.
16445 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16446 * chaining otherwise.
16447 */
16448 this.imgSrcSanitizationWhitelist = function(regexp) {
16449 if (isDefined(regexp)) {
16450 imgSrcSanitizationWhitelist = regexp;
16451 return this;
16452 }
16453 return imgSrcSanitizationWhitelist;
16454 };
16455
16456 this.$get = function() {
16457 return function sanitizeUri(uri, isImage) {
16458 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16459 var normalizedVal;
16460 normalizedVal = urlResolve(uri).href;
16461 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16462 return 'unsafe:' + normalizedVal;
16463 }
16464 return uri;
16465 };
16466 };
16467}
16468
16469/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16470 * Any commits to this file should be reviewed with security in mind. *
16471 * Changes to this file can potentially create security vulnerabilities. *
16472 * An approval from 2 Core members with history of modifying *
16473 * this file is required. *
16474 * *
16475 * Does the change somehow allow for arbitrary javascript to be executed? *
16476 * Or allows for someone to change the prototype of built-in objects? *
16477 * Or gives undesired access to variables likes document or window? *
16478 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16479
16480var $sceMinErr = minErr('$sce');
16481
16482var SCE_CONTEXTS = {
16483 HTML: 'html',
16484 CSS: 'css',
16485 URL: 'url',
16486 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16487 // url. (e.g. ng-include, script src, templateUrl)
16488 RESOURCE_URL: 'resourceUrl',
16489 JS: 'js'
16490};
16491
16492// Helper functions follow.
16493
16494function adjustMatcher(matcher) {
16495 if (matcher === 'self') {
16496 return matcher;
16497 } else if (isString(matcher)) {
16498 // Strings match exactly except for 2 wildcards - '*' and '**'.
16499 // '*' matches any character except those from the set ':/.?&'.
16500 // '**' matches any character (like .* in a RegExp).
16501 // More than 2 *'s raises an error as it's ill defined.
16502 if (matcher.indexOf('***') > -1) {
16503 throw $sceMinErr('iwcard',
16504 'Illegal sequence *** in string matcher. String: {0}', matcher);
16505 }
16506 matcher = escapeForRegexp(matcher).
16507 replace('\\*\\*', '.*').
16508 replace('\\*', '[^:/.?&;]*');
16509 return new RegExp('^' + matcher + '$');
16510 } else if (isRegExp(matcher)) {
16511 // The only other type of matcher allowed is a Regexp.
16512 // Match entire URL / disallow partial matches.
16513 // Flags are reset (i.e. no global, ignoreCase or multiline)
16514 return new RegExp('^' + matcher.source + '$');
16515 } else {
16516 throw $sceMinErr('imatcher',
16517 'Matchers may only be "self", string patterns or RegExp objects');
16518 }
16519}
16520
16521
16522function adjustMatchers(matchers) {
16523 var adjustedMatchers = [];
16524 if (isDefined(matchers)) {
16525 forEach(matchers, function(matcher) {
16526 adjustedMatchers.push(adjustMatcher(matcher));
16527 });
16528 }
16529 return adjustedMatchers;
16530}
16531
16532
16533/**
16534 * @ngdoc service
16535 * @name $sceDelegate
16536 * @kind function
16537 *
16538 * @description
16539 *
16540 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16541 * Contextual Escaping (SCE)} services to AngularJS.
16542 *
16543 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16544 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16545 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16546 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16547 * work because `$sce` delegates to `$sceDelegate` for these operations.
16548 *
16549 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16550 *
16551 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16552 * can override it completely to change the behavior of `$sce`, the common case would
16553 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16554 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16555 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16556 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16557 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16558 */
16559
16560/**
16561 * @ngdoc provider
16562 * @name $sceDelegateProvider
16563 * @description
16564 *
16565 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16566 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16567 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16568 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16569 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16570 *
16571 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16572 * Strict Contextual Escaping (SCE)}.
16573 *
16574 * **Example**: Consider the following case. <a name="example"></a>
16575 *
16576 * - your app is hosted at url `http://myapp.example.com/`
16577 * - but some of your templates are hosted on other domains you control such as
16578 * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc.
16579 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16580 *
16581 * Here is what a secure configuration for this scenario might look like:
16582 *
16583 * ```
16584 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16585 * $sceDelegateProvider.resourceUrlWhitelist([
16586 * // Allow same origin resource loads.
16587 * 'self',
16588 * // Allow loading from our assets domain. Notice the difference between * and **.
16589 * 'http://srv*.assets.example.com/**'
16590 * ]);
16591 *
16592 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16593 * $sceDelegateProvider.resourceUrlBlacklist([
16594 * 'http://myapp.example.com/clickThru**'
16595 * ]);
16596 * });
16597 * ```
16598 */
16599
16600function $SceDelegateProvider() {
16601 this.SCE_CONTEXTS = SCE_CONTEXTS;
16602
16603 // Resource URLs can also be trusted by policy.
16604 var resourceUrlWhitelist = ['self'],
16605 resourceUrlBlacklist = [];
16606
16607 /**
16608 * @ngdoc method
16609 * @name $sceDelegateProvider#resourceUrlWhitelist
16610 * @kind function
16611 *
16612 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16613 * provided. This must be an array or null. A snapshot of this array is used so further
16614 * changes to the array are ignored.
16615 *
16616 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16617 * allowed in this array.
16618 *
16619 * Note: **an empty whitelist array will block all URLs**!
16620 *
16621 * @return {Array} the currently set whitelist array.
16622 *
16623 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16624 * same origin resource requests.
16625 *
16626 * @description
16627 * Sets/Gets the whitelist of trusted resource URLs.
16628 */
16629 this.resourceUrlWhitelist = function(value) {
16630 if (arguments.length) {
16631 resourceUrlWhitelist = adjustMatchers(value);
16632 }
16633 return resourceUrlWhitelist;
16634 };
16635
16636 /**
16637 * @ngdoc method
16638 * @name $sceDelegateProvider#resourceUrlBlacklist
16639 * @kind function
16640 *
16641 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16642 * provided. This must be an array or null. A snapshot of this array is used so further
16643 * changes to the array are ignored.
16644 *
16645 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16646 * allowed in this array.
16647 *
16648 * The typical usage for the blacklist is to **block
16649 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16650 * these would otherwise be trusted but actually return content from the redirected domain.
16651 *
16652 * Finally, **the blacklist overrides the whitelist** and has the final say.
16653 *
16654 * @return {Array} the currently set blacklist array.
16655 *
16656 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16657 * is no blacklist.)
16658 *
16659 * @description
16660 * Sets/Gets the blacklist of trusted resource URLs.
16661 */
16662
16663 this.resourceUrlBlacklist = function(value) {
16664 if (arguments.length) {
16665 resourceUrlBlacklist = adjustMatchers(value);
16666 }
16667 return resourceUrlBlacklist;
16668 };
16669
16670 this.$get = ['$injector', function($injector) {
16671
16672 var htmlSanitizer = function htmlSanitizer(html) {
16673 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16674 };
16675
16676 if ($injector.has('$sanitize')) {
16677 htmlSanitizer = $injector.get('$sanitize');
16678 }
16679
16680
16681 function matchUrl(matcher, parsedUrl) {
16682 if (matcher === 'self') {
16683 return urlIsSameOrigin(parsedUrl);
16684 } else {
16685 // definitely a regex. See adjustMatchers()
16686 return !!matcher.exec(parsedUrl.href);
16687 }
16688 }
16689
16690 function isResourceUrlAllowedByPolicy(url) {
16691 var parsedUrl = urlResolve(url.toString());
16692 var i, n, allowed = false;
16693 // Ensure that at least one item from the whitelist allows this url.
16694 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16695 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16696 allowed = true;
16697 break;
16698 }
16699 }
16700 if (allowed) {
16701 // Ensure that no item from the blacklist blocked this url.
16702 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16703 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16704 allowed = false;
16705 break;
16706 }
16707 }
16708 }
16709 return allowed;
16710 }
16711
16712 function generateHolderType(Base) {
16713 var holderType = function TrustedValueHolderType(trustedValue) {
16714 this.$$unwrapTrustedValue = function() {
16715 return trustedValue;
16716 };
16717 };
16718 if (Base) {
16719 holderType.prototype = new Base();
16720 }
16721 holderType.prototype.valueOf = function sceValueOf() {
16722 return this.$$unwrapTrustedValue();
16723 };
16724 holderType.prototype.toString = function sceToString() {
16725 return this.$$unwrapTrustedValue().toString();
16726 };
16727 return holderType;
16728 }
16729
16730 var trustedValueHolderBase = generateHolderType(),
16731 byType = {};
16732
16733 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16734 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16735 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16736 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16737 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16738
16739 /**
16740 * @ngdoc method
16741 * @name $sceDelegate#trustAs
16742 *
16743 * @description
16744 * Returns an object that is trusted by angular for use in specified strict
16745 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16746 * attribute interpolation, any dom event binding attribute interpolation
16747 * such as for onclick, etc.) that uses the provided value.
16748 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16749 *
16750 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16751 * resourceUrl, html, js and css.
16752 * @param {*} value The value that that should be considered trusted/safe.
16753 * @returns {*} A value that can be used to stand in for the provided `value` in places
16754 * where Angular expects a $sce.trustAs() return value.
16755 */
16756 function trustAs(type, trustedValue) {
16757 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16758 if (!Constructor) {
16759 throw $sceMinErr('icontext',
16760 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16761 type, trustedValue);
16762 }
16763 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16764 return trustedValue;
16765 }
16766 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16767 // mutable objects, we ensure here that the value passed in is actually a string.
16768 if (typeof trustedValue !== 'string') {
16769 throw $sceMinErr('itype',
16770 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16771 type);
16772 }
16773 return new Constructor(trustedValue);
16774 }
16775
16776 /**
16777 * @ngdoc method
16778 * @name $sceDelegate#valueOf
16779 *
16780 * @description
16781 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16782 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16783 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16784 *
16785 * If the passed parameter is not a value that had been returned by {@link
16786 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16787 *
16788 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16789 * call or anything else.
16790 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16791 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16792 * `value` unchanged.
16793 */
16794 function valueOf(maybeTrusted) {
16795 if (maybeTrusted instanceof trustedValueHolderBase) {
16796 return maybeTrusted.$$unwrapTrustedValue();
16797 } else {
16798 return maybeTrusted;
16799 }
16800 }
16801
16802 /**
16803 * @ngdoc method
16804 * @name $sceDelegate#getTrusted
16805 *
16806 * @description
16807 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16808 * returns the originally supplied value if the queried context type is a supertype of the
16809 * created type. If this condition isn't satisfied, throws an exception.
16810 *
16811 * @param {string} type The kind of context in which this value is to be used.
16812 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16813 * `$sceDelegate.trustAs`} call.
16814 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16815 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16816 */
16817 function getTrusted(type, maybeTrusted) {
16818 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16819 return maybeTrusted;
16820 }
16821 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16822 if (constructor && maybeTrusted instanceof constructor) {
16823 return maybeTrusted.$$unwrapTrustedValue();
16824 }
16825 // If we get here, then we may only take one of two actions.
16826 // 1. sanitize the value for the requested type, or
16827 // 2. throw an exception.
16828 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16829 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16830 return maybeTrusted;
16831 } else {
16832 throw $sceMinErr('insecurl',
16833 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16834 maybeTrusted.toString());
16835 }
16836 } else if (type === SCE_CONTEXTS.HTML) {
16837 return htmlSanitizer(maybeTrusted);
16838 }
16839 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16840 }
16841
16842 return { trustAs: trustAs,
16843 getTrusted: getTrusted,
16844 valueOf: valueOf };
16845 }];
16846}
16847
16848
16849/**
16850 * @ngdoc provider
16851 * @name $sceProvider
16852 * @description
16853 *
16854 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16855 * - enable/disable Strict Contextual Escaping (SCE) in a module
16856 * - override the default implementation with a custom delegate
16857 *
16858 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
16859 */
16860
16861/* jshint maxlen: false*/
16862
16863/**
16864 * @ngdoc service
16865 * @name $sce
16866 * @kind function
16867 *
16868 * @description
16869 *
16870 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
16871 *
16872 * # Strict Contextual Escaping
16873 *
16874 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
16875 * contexts to result in a value that is marked as safe to use for that context. One example of
16876 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
16877 * to these contexts as privileged or SCE contexts.
16878 *
16879 * As of version 1.2, Angular ships with SCE enabled by default.
16880 *
16881 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
16882 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
16883 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
16884 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
16885 * to the top of your HTML document.
16886 *
16887 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
16888 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
16889 *
16890 * Here's an example of a binding in a privileged context:
16891 *
16892 * ```
16893 * <input ng-model="userHtml" aria-label="User input">
16894 * <div ng-bind-html="userHtml"></div>
16895 * ```
16896 *
16897 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
16898 * disabled, this application allows the user to render arbitrary HTML into the DIV.
16899 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
16900 * bindings. (HTML is just one example of a context where rendering user controlled input creates
16901 * security vulnerabilities.)
16902 *
16903 * For the case of HTML, you might use a library, either on the client side, or on the server side,
16904 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
16905 *
16906 * How would you ensure that every place that used these types of bindings was bound to a value that
16907 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
16908 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
16909 * properties/fields and forgot to update the binding to the sanitized value?
16910 *
16911 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
16912 * determine that something explicitly says it's safe to use a value for binding in that
16913 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
16914 * for those values that you can easily tell are safe - because they were received from your server,
16915 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
16916 * allowing only the files in a specific directory to do this. Ensuring that the internal API
16917 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
16918 *
16919 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
16920 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
16921 * obtain values that will be accepted by SCE / privileged contexts.
16922 *
16923 *
16924 * ## How does it work?
16925 *
16926 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
16927 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
16928 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
16929 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
16930 *
16931 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
16932 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
16933 * simplified):
16934 *
16935 * ```
16936 * var ngBindHtmlDirective = ['$sce', function($sce) {
16937 * return function(scope, element, attr) {
16938 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
16939 * element.html(value || '');
16940 * });
16941 * };
16942 * }];
16943 * ```
16944 *
16945 * ## Impact on loading templates
16946 *
16947 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
16948 * `templateUrl`'s specified by {@link guide/directive directives}.
16949 *
16950 * By default, Angular only loads templates from the same domain and protocol as the application
16951 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
16952 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
16953 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
16954 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
16955 *
16956 * *Please note*:
16957 * The browser's
16958 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
16959 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
16960 * policy apply in addition to this and may further restrict whether the template is successfully
16961 * loaded. This means that without the right CORS policy, loading templates from a different domain
16962 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
16963 * browsers.
16964 *
16965 * ## This feels like too much overhead
16966 *
16967 * It's important to remember that SCE only applies to interpolation expressions.
16968 *
16969 * If your expressions are constant literals, they're automatically trusted and you don't need to
16970 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
16971 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
16972 *
16973 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
16974 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
16975 *
16976 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
16977 * templates in `ng-include` from your application's domain without having to even know about SCE.
16978 * It blocks loading templates from other domains or loading templates over http from an https
16979 * served document. You can change these by setting your own custom {@link
16980 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
16981 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
16982 *
16983 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
16984 * application that's secure and can be audited to verify that with much more ease than bolting
16985 * security onto an application later.
16986 *
16987 * <a name="contexts"></a>
16988 * ## What trusted context types are supported?
16989 *
16990 * | Context | Notes |
16991 * |---------------------|----------------|
16992 * | `$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. |
16993 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
16994 * | `$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. |
16995 * | `$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. |
16996 * | `$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. |
16997 *
16998 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
16999 *
17000 * Each element in these arrays must be one of the following:
17001 *
17002 * - **'self'**
17003 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17004 * domain** as the application document using the **same protocol**.
17005 * - **String** (except the special value `'self'`)
17006 * - The string is matched against the full *normalized / absolute URL* of the resource
17007 * being tested (substring matches are not good enough.)
17008 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17009 * match themselves.
17010 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17011 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17012 * in a whitelist.
17013 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17014 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17015 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17016 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17017 * http://foo.example.com/templates/**).
17018 * - **RegExp** (*see caveat below*)
17019 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17020 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17021 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17022 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17023 * small number of cases. A `.` character in the regex used when matching the scheme or a
17024 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17025 * is highly recommended to use the string patterns and only fall back to regular expressions
17026 * as a last resort.
17027 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17028 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17029 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17030 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17031 * - If you are generating your JavaScript from some other templating engine (not
17032 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17033 * remember to escape your regular expression (and be aware that you might need more than
17034 * one level of escaping depending on your templating engine and the way you interpolated
17035 * the value.) Do make use of your platform's escaping mechanism as it might be good
17036 * enough before coding your own. E.g. Ruby has
17037 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17038 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17039 * Javascript lacks a similar built in function for escaping. Take a look at Google
17040 * Closure library's [goog.string.regExpEscape(s)](
17041 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17042 *
17043 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17044 *
17045 * ## Show me an example using SCE.
17046 *
17047 * <example module="mySceApp" deps="angular-sanitize.js">
17048 * <file name="index.html">
17049 * <div ng-controller="AppController as myCtrl">
17050 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17051 * <b>User comments</b><br>
17052 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17053 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17054 * exploit.
17055 * <div class="well">
17056 * <div ng-repeat="userComment in myCtrl.userComments">
17057 * <b>{{userComment.name}}</b>:
17058 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17059 * <br>
17060 * </div>
17061 * </div>
17062 * </div>
17063 * </file>
17064 *
17065 * <file name="script.js">
17066 * angular.module('mySceApp', ['ngSanitize'])
17067 * .controller('AppController', ['$http', '$templateCache', '$sce',
17068 * function($http, $templateCache, $sce) {
17069 * var self = this;
17070 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17071 * self.userComments = userComments;
17072 * });
17073 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17074 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
17075 * 'sanitization.&quot;">Hover over this text.</span>');
17076 * }]);
17077 * </file>
17078 *
17079 * <file name="test_data.json">
17080 * [
17081 * { "name": "Alice",
17082 * "htmlComment":
17083 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17084 * },
17085 * { "name": "Bob",
17086 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17087 * }
17088 * ]
17089 * </file>
17090 *
17091 * <file name="protractor.js" type="protractor">
17092 * describe('SCE doc demo', function() {
17093 * it('should sanitize untrusted values', function() {
17094 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17095 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17096 * });
17097 *
17098 * it('should NOT sanitize explicitly trusted values', function() {
17099 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17100 * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
17101 * 'sanitization.&quot;">Hover over this text.</span>');
17102 * });
17103 * });
17104 * </file>
17105 * </example>
17106 *
17107 *
17108 *
17109 * ## Can I disable SCE completely?
17110 *
17111 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17112 * for little coding overhead. It will be much harder to take an SCE disabled application and
17113 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17114 * for cases where you have a lot of existing code that was written before SCE was introduced and
17115 * you're migrating them a module at a time.
17116 *
17117 * That said, here's how you can completely disable SCE:
17118 *
17119 * ```
17120 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17121 * // Completely disable SCE. For demonstration purposes only!
17122 * // Do not use in new projects.
17123 * $sceProvider.enabled(false);
17124 * });
17125 * ```
17126 *
17127 */
17128/* jshint maxlen: 100 */
17129
17130function $SceProvider() {
17131 var enabled = true;
17132
17133 /**
17134 * @ngdoc method
17135 * @name $sceProvider#enabled
17136 * @kind function
17137 *
17138 * @param {boolean=} value If provided, then enables/disables SCE.
17139 * @return {boolean} true if SCE is enabled, false otherwise.
17140 *
17141 * @description
17142 * Enables/disables SCE and returns the current value.
17143 */
17144 this.enabled = function(value) {
17145 if (arguments.length) {
17146 enabled = !!value;
17147 }
17148 return enabled;
17149 };
17150
17151
17152 /* Design notes on the default implementation for SCE.
17153 *
17154 * The API contract for the SCE delegate
17155 * -------------------------------------
17156 * The SCE delegate object must provide the following 3 methods:
17157 *
17158 * - trustAs(contextEnum, value)
17159 * This method is used to tell the SCE service that the provided value is OK to use in the
17160 * contexts specified by contextEnum. It must return an object that will be accepted by
17161 * getTrusted() for a compatible contextEnum and return this value.
17162 *
17163 * - valueOf(value)
17164 * For values that were not produced by trustAs(), return them as is. For values that were
17165 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17166 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17167 * such a value.
17168 *
17169 * - getTrusted(contextEnum, value)
17170 * This function should return the a value that is safe to use in the context specified by
17171 * contextEnum or throw and exception otherwise.
17172 *
17173 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17174 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17175 * instance, an implementation could maintain a registry of all trusted objects by context. In
17176 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17177 * return the same object passed in if it was found in the registry under a compatible context or
17178 * throw an exception otherwise. An implementation might only wrap values some of the time based
17179 * on some criteria. getTrusted() might return a value and not throw an exception for special
17180 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17181 *
17182 *
17183 * A note on the inheritance model for SCE contexts
17184 * ------------------------------------------------
17185 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17186 * is purely an implementation details.
17187 *
17188 * The contract is simply this:
17189 *
17190 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17191 * will also succeed.
17192 *
17193 * Inheritance happens to capture this in a natural way. In some future, we
17194 * may not use inheritance anymore. That is OK because no code outside of
17195 * sce.js and sceSpecs.js would need to be aware of this detail.
17196 */
17197
17198 this.$get = ['$parse', '$sceDelegate', function(
17199 $parse, $sceDelegate) {
17200 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17201 // the "expression(javascript expression)" syntax which is insecure.
17202 if (enabled && msie < 8) {
17203 throw $sceMinErr('iequirks',
17204 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17205 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17206 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17207 }
17208
17209 var sce = shallowCopy(SCE_CONTEXTS);
17210
17211 /**
17212 * @ngdoc method
17213 * @name $sce#isEnabled
17214 * @kind function
17215 *
17216 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17217 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17218 *
17219 * @description
17220 * Returns a boolean indicating if SCE is enabled.
17221 */
17222 sce.isEnabled = function() {
17223 return enabled;
17224 };
17225 sce.trustAs = $sceDelegate.trustAs;
17226 sce.getTrusted = $sceDelegate.getTrusted;
17227 sce.valueOf = $sceDelegate.valueOf;
17228
17229 if (!enabled) {
17230 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17231 sce.valueOf = identity;
17232 }
17233
17234 /**
17235 * @ngdoc method
17236 * @name $sce#parseAs
17237 *
17238 * @description
17239 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17240 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17241 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17242 * *result*)}
17243 *
17244 * @param {string} type The kind of SCE context in which this result will be used.
17245 * @param {string} expression String expression to compile.
17246 * @returns {function(context, locals)} a function which represents the compiled expression:
17247 *
17248 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17249 * are evaluated against (typically a scope object).
17250 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17251 * `context`.
17252 */
17253 sce.parseAs = function sceParseAs(type, expr) {
17254 var parsed = $parse(expr);
17255 if (parsed.literal && parsed.constant) {
17256 return parsed;
17257 } else {
17258 return $parse(expr, function(value) {
17259 return sce.getTrusted(type, value);
17260 });
17261 }
17262 };
17263
17264 /**
17265 * @ngdoc method
17266 * @name $sce#trustAs
17267 *
17268 * @description
17269 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17270 * returns an object that is trusted by angular for use in specified strict contextual
17271 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17272 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17273 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17274 * escaping.
17275 *
17276 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17277 * resourceUrl, html, js and css.
17278 * @param {*} value The value that that should be considered trusted/safe.
17279 * @returns {*} A value that can be used to stand in for the provided `value` in places
17280 * where Angular expects a $sce.trustAs() return value.
17281 */
17282
17283 /**
17284 * @ngdoc method
17285 * @name $sce#trustAsHtml
17286 *
17287 * @description
17288 * Shorthand method. `$sce.trustAsHtml(value)` →
17289 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17290 *
17291 * @param {*} value The value to trustAs.
17292 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17293 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17294 * only accept expressions that are either literal constants or are the
17295 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17296 */
17297
17298 /**
17299 * @ngdoc method
17300 * @name $sce#trustAsUrl
17301 *
17302 * @description
17303 * Shorthand method. `$sce.trustAsUrl(value)` →
17304 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17305 *
17306 * @param {*} value The value to trustAs.
17307 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17308 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17309 * only accept expressions that are either literal constants or are the
17310 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17311 */
17312
17313 /**
17314 * @ngdoc method
17315 * @name $sce#trustAsResourceUrl
17316 *
17317 * @description
17318 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17319 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17320 *
17321 * @param {*} value The value to trustAs.
17322 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17323 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17324 * only accept expressions that are either literal constants or are the return
17325 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17326 */
17327
17328 /**
17329 * @ngdoc method
17330 * @name $sce#trustAsJs
17331 *
17332 * @description
17333 * Shorthand method. `$sce.trustAsJs(value)` →
17334 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17335 *
17336 * @param {*} value The value to trustAs.
17337 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17338 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17339 * only accept expressions that are either literal constants or are the
17340 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17341 */
17342
17343 /**
17344 * @ngdoc method
17345 * @name $sce#getTrusted
17346 *
17347 * @description
17348 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17349 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17350 * originally supplied value if the queried context type is a supertype of the created type.
17351 * If this condition isn't satisfied, throws an exception.
17352 *
17353 * @param {string} type The kind of context in which this value is to be used.
17354 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17355 * call.
17356 * @returns {*} The value the was originally provided to
17357 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17358 * Otherwise, throws an exception.
17359 */
17360
17361 /**
17362 * @ngdoc method
17363 * @name $sce#getTrustedHtml
17364 *
17365 * @description
17366 * Shorthand method. `$sce.getTrustedHtml(value)` →
17367 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17368 *
17369 * @param {*} value The value to pass to `$sce.getTrusted`.
17370 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17371 */
17372
17373 /**
17374 * @ngdoc method
17375 * @name $sce#getTrustedCss
17376 *
17377 * @description
17378 * Shorthand method. `$sce.getTrustedCss(value)` →
17379 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17380 *
17381 * @param {*} value The value to pass to `$sce.getTrusted`.
17382 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17383 */
17384
17385 /**
17386 * @ngdoc method
17387 * @name $sce#getTrustedUrl
17388 *
17389 * @description
17390 * Shorthand method. `$sce.getTrustedUrl(value)` →
17391 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17392 *
17393 * @param {*} value The value to pass to `$sce.getTrusted`.
17394 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17395 */
17396
17397 /**
17398 * @ngdoc method
17399 * @name $sce#getTrustedResourceUrl
17400 *
17401 * @description
17402 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17403 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17404 *
17405 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17406 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17407 */
17408
17409 /**
17410 * @ngdoc method
17411 * @name $sce#getTrustedJs
17412 *
17413 * @description
17414 * Shorthand method. `$sce.getTrustedJs(value)` →
17415 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17416 *
17417 * @param {*} value The value to pass to `$sce.getTrusted`.
17418 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17419 */
17420
17421 /**
17422 * @ngdoc method
17423 * @name $sce#parseAsHtml
17424 *
17425 * @description
17426 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17427 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17428 *
17429 * @param {string} expression String expression to compile.
17430 * @returns {function(context, locals)} a function which represents the compiled expression:
17431 *
17432 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17433 * are evaluated against (typically a scope object).
17434 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17435 * `context`.
17436 */
17437
17438 /**
17439 * @ngdoc method
17440 * @name $sce#parseAsCss
17441 *
17442 * @description
17443 * Shorthand method. `$sce.parseAsCss(value)` →
17444 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17445 *
17446 * @param {string} expression String expression to compile.
17447 * @returns {function(context, locals)} a function which represents the compiled expression:
17448 *
17449 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17450 * are evaluated against (typically a scope object).
17451 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17452 * `context`.
17453 */
17454
17455 /**
17456 * @ngdoc method
17457 * @name $sce#parseAsUrl
17458 *
17459 * @description
17460 * Shorthand method. `$sce.parseAsUrl(value)` →
17461 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17462 *
17463 * @param {string} expression String expression to compile.
17464 * @returns {function(context, locals)} a function which represents the compiled expression:
17465 *
17466 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17467 * are evaluated against (typically a scope object).
17468 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17469 * `context`.
17470 */
17471
17472 /**
17473 * @ngdoc method
17474 * @name $sce#parseAsResourceUrl
17475 *
17476 * @description
17477 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17478 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17479 *
17480 * @param {string} expression String expression to compile.
17481 * @returns {function(context, locals)} a function which represents the compiled expression:
17482 *
17483 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17484 * are evaluated against (typically a scope object).
17485 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17486 * `context`.
17487 */
17488
17489 /**
17490 * @ngdoc method
17491 * @name $sce#parseAsJs
17492 *
17493 * @description
17494 * Shorthand method. `$sce.parseAsJs(value)` →
17495 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17496 *
17497 * @param {string} expression String expression to compile.
17498 * @returns {function(context, locals)} a function which represents the compiled expression:
17499 *
17500 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17501 * are evaluated against (typically a scope object).
17502 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17503 * `context`.
17504 */
17505
17506 // Shorthand delegations.
17507 var parse = sce.parseAs,
17508 getTrusted = sce.getTrusted,
17509 trustAs = sce.trustAs;
17510
17511 forEach(SCE_CONTEXTS, function(enumValue, name) {
17512 var lName = lowercase(name);
17513 sce[camelCase("parse_as_" + lName)] = function(expr) {
17514 return parse(enumValue, expr);
17515 };
17516 sce[camelCase("get_trusted_" + lName)] = function(value) {
17517 return getTrusted(enumValue, value);
17518 };
17519 sce[camelCase("trust_as_" + lName)] = function(value) {
17520 return trustAs(enumValue, value);
17521 };
17522 });
17523
17524 return sce;
17525 }];
17526}
17527
17528/**
17529 * !!! This is an undocumented "private" service !!!
17530 *
17531 * @name $sniffer
17532 * @requires $window
17533 * @requires $document
17534 *
17535 * @property {boolean} history Does the browser support html5 history api ?
17536 * @property {boolean} transitions Does the browser support CSS transition events ?
17537 * @property {boolean} animations Does the browser support CSS animation events ?
17538 *
17539 * @description
17540 * This is very simple implementation of testing browser's features.
17541 */
17542function $SnifferProvider() {
17543 this.$get = ['$window', '$document', function($window, $document) {
17544 var eventSupport = {},
17545 android =
17546 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17547 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17548 document = $document[0] || {},
17549 vendorPrefix,
17550 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17551 bodyStyle = document.body && document.body.style,
17552 transitions = false,
17553 animations = false,
17554 match;
17555
17556 if (bodyStyle) {
17557 for (var prop in bodyStyle) {
17558 if (match = vendorRegex.exec(prop)) {
17559 vendorPrefix = match[0];
17560 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17561 break;
17562 }
17563 }
17564
17565 if (!vendorPrefix) {
17566 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17567 }
17568
17569 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17570 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17571
17572 if (android && (!transitions || !animations)) {
17573 transitions = isString(bodyStyle.webkitTransition);
17574 animations = isString(bodyStyle.webkitAnimation);
17575 }
17576 }
17577
17578
17579 return {
17580 // Android has history.pushState, but it does not update location correctly
17581 // so let's not use the history API at all.
17582 // http://code.google.com/p/android/issues/detail?id=17471
17583 // https://github.com/angular/angular.js/issues/904
17584
17585 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17586 // so let's not use the history API also
17587 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17588 // jshint -W018
17589 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17590 // jshint +W018
17591 hasEvent: function(event) {
17592 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17593 // it. In particular the event is not fired when backspace or delete key are pressed or
17594 // when cut operation is performed.
17595 // IE10+ implements 'input' event but it erroneously fires under various situations,
17596 // e.g. when placeholder changes, or a form is focused.
17597 if (event === 'input' && msie <= 11) return false;
17598
17599 if (isUndefined(eventSupport[event])) {
17600 var divElm = document.createElement('div');
17601 eventSupport[event] = 'on' + event in divElm;
17602 }
17603
17604 return eventSupport[event];
17605 },
17606 csp: csp(),
17607 vendorPrefix: vendorPrefix,
17608 transitions: transitions,
17609 animations: animations,
17610 android: android
17611 };
17612 }];
17613}
17614
17615var $compileMinErr = minErr('$compile');
17616
17617/**
17618 * @ngdoc service
17619 * @name $templateRequest
17620 *
17621 * @description
17622 * The `$templateRequest` service runs security checks then downloads the provided template using
17623 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17624 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17625 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17626 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17627 * when `tpl` is of type string and `$templateCache` has the matching entry.
17628 *
17629 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17630 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17631 *
17632 * @return {Promise} a promise for the HTTP response data of the given URL.
17633 *
17634 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17635 */
17636function $TemplateRequestProvider() {
17637 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17638 function handleRequestFn(tpl, ignoreRequestError) {
17639 handleRequestFn.totalPendingRequests++;
17640
17641 // We consider the template cache holds only trusted templates, so
17642 // there's no need to go through whitelisting again for keys that already
17643 // are included in there. This also makes Angular accept any script
17644 // directive, no matter its name. However, we still need to unwrap trusted
17645 // types.
17646 if (!isString(tpl) || !$templateCache.get(tpl)) {
17647 tpl = $sce.getTrustedResourceUrl(tpl);
17648 }
17649
17650 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17651
17652 if (isArray(transformResponse)) {
17653 transformResponse = transformResponse.filter(function(transformer) {
17654 return transformer !== defaultHttpResponseTransform;
17655 });
17656 } else if (transformResponse === defaultHttpResponseTransform) {
17657 transformResponse = null;
17658 }
17659
17660 var httpOptions = {
17661 cache: $templateCache,
17662 transformResponse: transformResponse
17663 };
17664
17665 return $http.get(tpl, httpOptions)
17666 ['finally'](function() {
17667 handleRequestFn.totalPendingRequests--;
17668 })
17669 .then(function(response) {
17670 $templateCache.put(tpl, response.data);
17671 return response.data;
17672 }, handleError);
17673
17674 function handleError(resp) {
17675 if (!ignoreRequestError) {
17676 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17677 tpl, resp.status, resp.statusText);
17678 }
17679 return $q.reject(resp);
17680 }
17681 }
17682
17683 handleRequestFn.totalPendingRequests = 0;
17684
17685 return handleRequestFn;
17686 }];
17687}
17688
17689function $$TestabilityProvider() {
17690 this.$get = ['$rootScope', '$browser', '$location',
17691 function($rootScope, $browser, $location) {
17692
17693 /**
17694 * @name $testability
17695 *
17696 * @description
17697 * The private $$testability service provides a collection of methods for use when debugging
17698 * or by automated test and debugging tools.
17699 */
17700 var testability = {};
17701
17702 /**
17703 * @name $$testability#findBindings
17704 *
17705 * @description
17706 * Returns an array of elements that are bound (via ng-bind or {{}})
17707 * to expressions matching the input.
17708 *
17709 * @param {Element} element The element root to search from.
17710 * @param {string} expression The binding expression to match.
17711 * @param {boolean} opt_exactMatch If true, only returns exact matches
17712 * for the expression. Filters and whitespace are ignored.
17713 */
17714 testability.findBindings = function(element, expression, opt_exactMatch) {
17715 var bindings = element.getElementsByClassName('ng-binding');
17716 var matches = [];
17717 forEach(bindings, function(binding) {
17718 var dataBinding = angular.element(binding).data('$binding');
17719 if (dataBinding) {
17720 forEach(dataBinding, function(bindingName) {
17721 if (opt_exactMatch) {
17722 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17723 if (matcher.test(bindingName)) {
17724 matches.push(binding);
17725 }
17726 } else {
17727 if (bindingName.indexOf(expression) != -1) {
17728 matches.push(binding);
17729 }
17730 }
17731 });
17732 }
17733 });
17734 return matches;
17735 };
17736
17737 /**
17738 * @name $$testability#findModels
17739 *
17740 * @description
17741 * Returns an array of elements that are two-way found via ng-model to
17742 * expressions matching the input.
17743 *
17744 * @param {Element} element The element root to search from.
17745 * @param {string} expression The model expression to match.
17746 * @param {boolean} opt_exactMatch If true, only returns exact matches
17747 * for the expression.
17748 */
17749 testability.findModels = function(element, expression, opt_exactMatch) {
17750 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17751 for (var p = 0; p < prefixes.length; ++p) {
17752 var attributeEquals = opt_exactMatch ? '=' : '*=';
17753 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17754 var elements = element.querySelectorAll(selector);
17755 if (elements.length) {
17756 return elements;
17757 }
17758 }
17759 };
17760
17761 /**
17762 * @name $$testability#getLocation
17763 *
17764 * @description
17765 * Shortcut for getting the location in a browser agnostic way. Returns
17766 * the path, search, and hash. (e.g. /path?a=b#hash)
17767 */
17768 testability.getLocation = function() {
17769 return $location.url();
17770 };
17771
17772 /**
17773 * @name $$testability#setLocation
17774 *
17775 * @description
17776 * Shortcut for navigating to a location without doing a full page reload.
17777 *
17778 * @param {string} url The location url (path, search and hash,
17779 * e.g. /path?a=b#hash) to go to.
17780 */
17781 testability.setLocation = function(url) {
17782 if (url !== $location.url()) {
17783 $location.url(url);
17784 $rootScope.$digest();
17785 }
17786 };
17787
17788 /**
17789 * @name $$testability#whenStable
17790 *
17791 * @description
17792 * Calls the callback when $timeout and $http requests are completed.
17793 *
17794 * @param {function} callback
17795 */
17796 testability.whenStable = function(callback) {
17797 $browser.notifyWhenNoOutstandingRequests(callback);
17798 };
17799
17800 return testability;
17801 }];
17802}
17803
17804function $TimeoutProvider() {
17805 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17806 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17807
17808 var deferreds = {};
17809
17810
17811 /**
17812 * @ngdoc service
17813 * @name $timeout
17814 *
17815 * @description
17816 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17817 * block and delegates any exceptions to
17818 * {@link ng.$exceptionHandler $exceptionHandler} service.
17819 *
17820 * The return value of calling `$timeout` is a promise, which will be resolved when
17821 * the delay has passed and the timeout function, if provided, is executed.
17822 *
17823 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17824 *
17825 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17826 * synchronously flush the queue of deferred functions.
17827 *
17828 * If you only want a promise that will be resolved after some specified delay
17829 * then you can call `$timeout` without the `fn` function.
17830 *
17831 * @param {function()=} fn A function, whose execution should be delayed.
17832 * @param {number=} [delay=0] Delay in milliseconds.
17833 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17834 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17835 * @param {...*=} Pass additional parameters to the executed function.
17836 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17837 * promise will be resolved with is the return value of the `fn` function.
17838 *
17839 */
17840 function timeout(fn, delay, invokeApply) {
17841 if (!isFunction(fn)) {
17842 invokeApply = delay;
17843 delay = fn;
17844 fn = noop;
17845 }
17846
17847 var args = sliceArgs(arguments, 3),
17848 skipApply = (isDefined(invokeApply) && !invokeApply),
17849 deferred = (skipApply ? $$q : $q).defer(),
17850 promise = deferred.promise,
17851 timeoutId;
17852
17853 timeoutId = $browser.defer(function() {
17854 try {
17855 deferred.resolve(fn.apply(null, args));
17856 } catch (e) {
17857 deferred.reject(e);
17858 $exceptionHandler(e);
17859 }
17860 finally {
17861 delete deferreds[promise.$$timeoutId];
17862 }
17863
17864 if (!skipApply) $rootScope.$apply();
17865 }, delay);
17866
17867 promise.$$timeoutId = timeoutId;
17868 deferreds[timeoutId] = deferred;
17869
17870 return promise;
17871 }
17872
17873
17874 /**
17875 * @ngdoc method
17876 * @name $timeout#cancel
17877 *
17878 * @description
17879 * Cancels a task associated with the `promise`. As a result of this, the promise will be
17880 * resolved with a rejection.
17881 *
17882 * @param {Promise=} promise Promise returned by the `$timeout` function.
17883 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
17884 * canceled.
17885 */
17886 timeout.cancel = function(promise) {
17887 if (promise && promise.$$timeoutId in deferreds) {
17888 deferreds[promise.$$timeoutId].reject('canceled');
17889 delete deferreds[promise.$$timeoutId];
17890 return $browser.defer.cancel(promise.$$timeoutId);
17891 }
17892 return false;
17893 };
17894
17895 return timeout;
17896 }];
17897}
17898
17899// NOTE: The usage of window and document instead of $window and $document here is
17900// deliberate. This service depends on the specific behavior of anchor nodes created by the
17901// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
17902// cause us to break tests. In addition, when the browser resolves a URL for XHR, it
17903// doesn't know about mocked locations and resolves URLs to the real document - which is
17904// exactly the behavior needed here. There is little value is mocking these out for this
17905// service.
17906var urlParsingNode = document.createElement("a");
17907var originUrl = urlResolve(window.location.href);
17908
17909
17910/**
17911 *
17912 * Implementation Notes for non-IE browsers
17913 * ----------------------------------------
17914 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
17915 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
17916 * URL will be resolved into an absolute URL in the context of the application document.
17917 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
17918 * properties are all populated to reflect the normalized URL. This approach has wide
17919 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
17920 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
17921 *
17922 * Implementation Notes for IE
17923 * ---------------------------
17924 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
17925 * browsers. However, the parsed components will not be set if the URL assigned did not specify
17926 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
17927 * work around that by performing the parsing in a 2nd step by taking a previously normalized
17928 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
17929 * properties such as protocol, hostname, port, etc.
17930 *
17931 * References:
17932 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
17933 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
17934 * http://url.spec.whatwg.org/#urlutils
17935 * https://github.com/angular/angular.js/pull/2902
17936 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
17937 *
17938 * @kind function
17939 * @param {string} url The URL to be parsed.
17940 * @description Normalizes and parses a URL.
17941 * @returns {object} Returns the normalized URL as a dictionary.
17942 *
17943 * | member name | Description |
17944 * |---------------|----------------|
17945 * | href | A normalized version of the provided URL if it was not an absolute URL |
17946 * | protocol | The protocol including the trailing colon |
17947 * | host | The host and port (if the port is non-default) of the normalizedUrl |
17948 * | search | The search params, minus the question mark |
17949 * | hash | The hash string, minus the hash symbol
17950 * | hostname | The hostname
17951 * | port | The port, without ":"
17952 * | pathname | The pathname, beginning with "/"
17953 *
17954 */
17955function urlResolve(url) {
17956 var href = url;
17957
17958 if (msie) {
17959 // Normalize before parse. Refer Implementation Notes on why this is
17960 // done in two steps on IE.
17961 urlParsingNode.setAttribute("href", href);
17962 href = urlParsingNode.href;
17963 }
17964
17965 urlParsingNode.setAttribute('href', href);
17966
17967 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
17968 return {
17969 href: urlParsingNode.href,
17970 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
17971 host: urlParsingNode.host,
17972 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
17973 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
17974 hostname: urlParsingNode.hostname,
17975 port: urlParsingNode.port,
17976 pathname: (urlParsingNode.pathname.charAt(0) === '/')
17977 ? urlParsingNode.pathname
17978 : '/' + urlParsingNode.pathname
17979 };
17980}
17981
17982/**
17983 * Parse a request URL and determine whether this is a same-origin request as the application document.
17984 *
17985 * @param {string|object} requestUrl The url of the request as a string that will be resolved
17986 * or a parsed URL object.
17987 * @returns {boolean} Whether the request is for the same origin as the application document.
17988 */
17989function urlIsSameOrigin(requestUrl) {
17990 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
17991 return (parsed.protocol === originUrl.protocol &&
17992 parsed.host === originUrl.host);
17993}
17994
17995/**
17996 * @ngdoc service
17997 * @name $window
17998 *
17999 * @description
18000 * A reference to the browser's `window` object. While `window`
18001 * is globally available in JavaScript, it causes testability problems, because
18002 * it is a global variable. In angular we always refer to it through the
18003 * `$window` service, so it may be overridden, removed or mocked for testing.
18004 *
18005 * Expressions, like the one defined for the `ngClick` directive in the example
18006 * below, are evaluated with respect to the current scope. Therefore, there is
18007 * no risk of inadvertently coding in a dependency on a global value in such an
18008 * expression.
18009 *
18010 * @example
18011 <example module="windowExample">
18012 <file name="index.html">
18013 <script>
18014 angular.module('windowExample', [])
18015 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18016 $scope.greeting = 'Hello, World!';
18017 $scope.doGreeting = function(greeting) {
18018 $window.alert(greeting);
18019 };
18020 }]);
18021 </script>
18022 <div ng-controller="ExampleController">
18023 <input type="text" ng-model="greeting" aria-label="greeting" />
18024 <button ng-click="doGreeting(greeting)">ALERT</button>
18025 </div>
18026 </file>
18027 <file name="protractor.js" type="protractor">
18028 it('should display the greeting in the input box', function() {
18029 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18030 // If we click the button it will block the test runner
18031 // element(':button').click();
18032 });
18033 </file>
18034 </example>
18035 */
18036function $WindowProvider() {
18037 this.$get = valueFn(window);
18038}
18039
18040/**
18041 * @name $$cookieReader
18042 * @requires $document
18043 *
18044 * @description
18045 * This is a private service for reading cookies used by $http and ngCookies
18046 *
18047 * @return {Object} a key/value map of the current cookies
18048 */
18049function $$CookieReader($document) {
18050 var rawDocument = $document[0] || {};
18051 var lastCookies = {};
18052 var lastCookieString = '';
18053
18054 function safeDecodeURIComponent(str) {
18055 try {
18056 return decodeURIComponent(str);
18057 } catch (e) {
18058 return str;
18059 }
18060 }
18061
18062 return function() {
18063 var cookieArray, cookie, i, index, name;
18064 var currentCookieString = rawDocument.cookie || '';
18065
18066 if (currentCookieString !== lastCookieString) {
18067 lastCookieString = currentCookieString;
18068 cookieArray = lastCookieString.split('; ');
18069 lastCookies = {};
18070
18071 for (i = 0; i < cookieArray.length; i++) {
18072 cookie = cookieArray[i];
18073 index = cookie.indexOf('=');
18074 if (index > 0) { //ignore nameless cookies
18075 name = safeDecodeURIComponent(cookie.substring(0, index));
18076 // the first value that is seen for a cookie is the most
18077 // specific one. values for the same cookie name that
18078 // follow are for less specific paths.
18079 if (isUndefined(lastCookies[name])) {
18080 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18081 }
18082 }
18083 }
18084 }
18085 return lastCookies;
18086 };
18087}
18088
18089$$CookieReader.$inject = ['$document'];
18090
18091function $$CookieReaderProvider() {
18092 this.$get = $$CookieReader;
18093}
18094
18095/* global currencyFilter: true,
18096 dateFilter: true,
18097 filterFilter: true,
18098 jsonFilter: true,
18099 limitToFilter: true,
18100 lowercaseFilter: true,
18101 numberFilter: true,
18102 orderByFilter: true,
18103 uppercaseFilter: true,
18104 */
18105
18106/**
18107 * @ngdoc provider
18108 * @name $filterProvider
18109 * @description
18110 *
18111 * Filters are just functions which transform input to an output. However filters need to be
18112 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18113 * annotated with dependencies and is responsible for creating a filter function.
18114 *
18115 * <div class="alert alert-warning">
18116 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18117 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18118 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18119 * (`myapp_subsection_filterx`).
18120 * </div>
18121 *
18122 * ```js
18123 * // Filter registration
18124 * function MyModule($provide, $filterProvider) {
18125 * // create a service to demonstrate injection (not always needed)
18126 * $provide.value('greet', function(name){
18127 * return 'Hello ' + name + '!';
18128 * });
18129 *
18130 * // register a filter factory which uses the
18131 * // greet service to demonstrate DI.
18132 * $filterProvider.register('greet', function(greet){
18133 * // return the filter function which uses the greet service
18134 * // to generate salutation
18135 * return function(text) {
18136 * // filters need to be forgiving so check input validity
18137 * return text && greet(text) || text;
18138 * };
18139 * });
18140 * }
18141 * ```
18142 *
18143 * The filter function is registered with the `$injector` under the filter name suffix with
18144 * `Filter`.
18145 *
18146 * ```js
18147 * it('should be the same instance', inject(
18148 * function($filterProvider) {
18149 * $filterProvider.register('reverse', function(){
18150 * return ...;
18151 * });
18152 * },
18153 * function($filter, reverseFilter) {
18154 * expect($filter('reverse')).toBe(reverseFilter);
18155 * });
18156 * ```
18157 *
18158 *
18159 * For more information about how angular filters work, and how to create your own filters, see
18160 * {@link guide/filter Filters} in the Angular Developer Guide.
18161 */
18162
18163/**
18164 * @ngdoc service
18165 * @name $filter
18166 * @kind function
18167 * @description
18168 * Filters are used for formatting data displayed to the user.
18169 *
18170 * The general syntax in templates is as follows:
18171 *
18172 * {{ expression [| filter_name[:parameter_value] ... ] }}
18173 *
18174 * @param {String} name Name of the filter function to retrieve
18175 * @return {Function} the filter function
18176 * @example
18177 <example name="$filter" module="filterExample">
18178 <file name="index.html">
18179 <div ng-controller="MainCtrl">
18180 <h3>{{ originalText }}</h3>
18181 <h3>{{ filteredText }}</h3>
18182 </div>
18183 </file>
18184
18185 <file name="script.js">
18186 angular.module('filterExample', [])
18187 .controller('MainCtrl', function($scope, $filter) {
18188 $scope.originalText = 'hello';
18189 $scope.filteredText = $filter('uppercase')($scope.originalText);
18190 });
18191 </file>
18192 </example>
18193 */
18194$FilterProvider.$inject = ['$provide'];
18195function $FilterProvider($provide) {
18196 var suffix = 'Filter';
18197
18198 /**
18199 * @ngdoc method
18200 * @name $filterProvider#register
18201 * @param {string|Object} name Name of the filter function, or an object map of filters where
18202 * the keys are the filter names and the values are the filter factories.
18203 *
18204 * <div class="alert alert-warning">
18205 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18206 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18207 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18208 * (`myapp_subsection_filterx`).
18209 * </div>
18210 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18211 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18212 * of the registered filter instances.
18213 */
18214 function register(name, factory) {
18215 if (isObject(name)) {
18216 var filters = {};
18217 forEach(name, function(filter, key) {
18218 filters[key] = register(key, filter);
18219 });
18220 return filters;
18221 } else {
18222 return $provide.factory(name + suffix, factory);
18223 }
18224 }
18225 this.register = register;
18226
18227 this.$get = ['$injector', function($injector) {
18228 return function(name) {
18229 return $injector.get(name + suffix);
18230 };
18231 }];
18232
18233 ////////////////////////////////////////
18234
18235 /* global
18236 currencyFilter: false,
18237 dateFilter: false,
18238 filterFilter: false,
18239 jsonFilter: false,
18240 limitToFilter: false,
18241 lowercaseFilter: false,
18242 numberFilter: false,
18243 orderByFilter: false,
18244 uppercaseFilter: false,
18245 */
18246
18247 register('currency', currencyFilter);
18248 register('date', dateFilter);
18249 register('filter', filterFilter);
18250 register('json', jsonFilter);
18251 register('limitTo', limitToFilter);
18252 register('lowercase', lowercaseFilter);
18253 register('number', numberFilter);
18254 register('orderBy', orderByFilter);
18255 register('uppercase', uppercaseFilter);
18256}
18257
18258/**
18259 * @ngdoc filter
18260 * @name filter
18261 * @kind function
18262 *
18263 * @description
18264 * Selects a subset of items from `array` and returns it as a new array.
18265 *
18266 * @param {Array} array The source array.
18267 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18268 * `array`.
18269 *
18270 * Can be one of:
18271 *
18272 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18273 * objects with string properties in `array` that match this string will be returned. This also
18274 * applies to nested object properties.
18275 * The predicate can be negated by prefixing the string with `!`.
18276 *
18277 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18278 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18279 * which have property `name` containing "M" and property `phone` containing "1". A special
18280 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18281 * property of the object or its nested object properties. That's equivalent to the simple
18282 * substring match with a `string` as described above. The predicate can be negated by prefixing
18283 * the string with `!`.
18284 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18285 * not containing "M".
18286 *
18287 * Note that a named property will match properties on the same level only, while the special
18288 * `$` property will match properties on the same level or deeper. E.g. an array item like
18289 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18290 * **will** be matched by `{$: 'John'}`.
18291 *
18292 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18293 * The function is called for each element of the array, with the element, its index, and
18294 * the entire array itself as arguments.
18295 *
18296 * The final result is an array of those elements that the predicate returned true for.
18297 *
18298 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18299 * determining if the expected value (from the filter expression) and actual value (from
18300 * the object in the array) should be considered a match.
18301 *
18302 * Can be one of:
18303 *
18304 * - `function(actual, expected)`:
18305 * The function will be given the object value and the predicate value to compare and
18306 * should return true if both values should be considered equal.
18307 *
18308 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18309 * This is essentially strict comparison of expected and actual.
18310 *
18311 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18312 * insensitive way.
18313 *
18314 * Primitive values are converted to strings. Objects are not compared against primitives,
18315 * unless they have a custom `toString` method (e.g. `Date` objects).
18316 *
18317 * @example
18318 <example>
18319 <file name="index.html">
18320 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18321 {name:'Mary', phone:'800-BIG-MARY'},
18322 {name:'Mike', phone:'555-4321'},
18323 {name:'Adam', phone:'555-5678'},
18324 {name:'Julie', phone:'555-8765'},
18325 {name:'Juliette', phone:'555-5678'}]"></div>
18326
18327 <label>Search: <input ng-model="searchText"></label>
18328 <table id="searchTextResults">
18329 <tr><th>Name</th><th>Phone</th></tr>
18330 <tr ng-repeat="friend in friends | filter:searchText">
18331 <td>{{friend.name}}</td>
18332 <td>{{friend.phone}}</td>
18333 </tr>
18334 </table>
18335 <hr>
18336 <label>Any: <input ng-model="search.$"></label> <br>
18337 <label>Name only <input ng-model="search.name"></label><br>
18338 <label>Phone only <input ng-model="search.phone"></label><br>
18339 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18340 <table id="searchObjResults">
18341 <tr><th>Name</th><th>Phone</th></tr>
18342 <tr ng-repeat="friendObj in friends | filter:search:strict">
18343 <td>{{friendObj.name}}</td>
18344 <td>{{friendObj.phone}}</td>
18345 </tr>
18346 </table>
18347 </file>
18348 <file name="protractor.js" type="protractor">
18349 var expectFriendNames = function(expectedNames, key) {
18350 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18351 arr.forEach(function(wd, i) {
18352 expect(wd.getText()).toMatch(expectedNames[i]);
18353 });
18354 });
18355 };
18356
18357 it('should search across all fields when filtering with a string', function() {
18358 var searchText = element(by.model('searchText'));
18359 searchText.clear();
18360 searchText.sendKeys('m');
18361 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18362
18363 searchText.clear();
18364 searchText.sendKeys('76');
18365 expectFriendNames(['John', 'Julie'], 'friend');
18366 });
18367
18368 it('should search in specific fields when filtering with a predicate object', function() {
18369 var searchAny = element(by.model('search.$'));
18370 searchAny.clear();
18371 searchAny.sendKeys('i');
18372 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18373 });
18374 it('should use a equal comparison when comparator is true', function() {
18375 var searchName = element(by.model('search.name'));
18376 var strict = element(by.model('strict'));
18377 searchName.clear();
18378 searchName.sendKeys('Julie');
18379 strict.click();
18380 expectFriendNames(['Julie'], 'friendObj');
18381 });
18382 </file>
18383 </example>
18384 */
18385function filterFilter() {
18386 return function(array, expression, comparator) {
18387 if (!isArrayLike(array)) {
18388 if (array == null) {
18389 return array;
18390 } else {
18391 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18392 }
18393 }
18394
18395 var expressionType = getTypeForFilter(expression);
18396 var predicateFn;
18397 var matchAgainstAnyProp;
18398
18399 switch (expressionType) {
18400 case 'function':
18401 predicateFn = expression;
18402 break;
18403 case 'boolean':
18404 case 'null':
18405 case 'number':
18406 case 'string':
18407 matchAgainstAnyProp = true;
18408 //jshint -W086
18409 case 'object':
18410 //jshint +W086
18411 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18412 break;
18413 default:
18414 return array;
18415 }
18416
18417 return Array.prototype.filter.call(array, predicateFn);
18418 };
18419}
18420
18421// Helper functions for `filterFilter`
18422function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18423 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18424 var predicateFn;
18425
18426 if (comparator === true) {
18427 comparator = equals;
18428 } else if (!isFunction(comparator)) {
18429 comparator = function(actual, expected) {
18430 if (isUndefined(actual)) {
18431 // No substring matching against `undefined`
18432 return false;
18433 }
18434 if ((actual === null) || (expected === null)) {
18435 // No substring matching against `null`; only match against `null`
18436 return actual === expected;
18437 }
18438 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18439 // Should not compare primitives against objects, unless they have custom `toString` method
18440 return false;
18441 }
18442
18443 actual = lowercase('' + actual);
18444 expected = lowercase('' + expected);
18445 return actual.indexOf(expected) !== -1;
18446 };
18447 }
18448
18449 predicateFn = function(item) {
18450 if (shouldMatchPrimitives && !isObject(item)) {
18451 return deepCompare(item, expression.$, comparator, false);
18452 }
18453 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18454 };
18455
18456 return predicateFn;
18457}
18458
18459function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18460 var actualType = getTypeForFilter(actual);
18461 var expectedType = getTypeForFilter(expected);
18462
18463 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18464 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18465 } else if (isArray(actual)) {
18466 // In case `actual` is an array, consider it a match
18467 // if ANY of it's items matches `expected`
18468 return actual.some(function(item) {
18469 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18470 });
18471 }
18472
18473 switch (actualType) {
18474 case 'object':
18475 var key;
18476 if (matchAgainstAnyProp) {
18477 for (key in actual) {
18478 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18479 return true;
18480 }
18481 }
18482 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18483 } else if (expectedType === 'object') {
18484 for (key in expected) {
18485 var expectedVal = expected[key];
18486 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18487 continue;
18488 }
18489
18490 var matchAnyProperty = key === '$';
18491 var actualVal = matchAnyProperty ? actual : actual[key];
18492 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18493 return false;
18494 }
18495 }
18496 return true;
18497 } else {
18498 return comparator(actual, expected);
18499 }
18500 break;
18501 case 'function':
18502 return false;
18503 default:
18504 return comparator(actual, expected);
18505 }
18506}
18507
18508// Used for easily differentiating between `null` and actual `object`
18509function getTypeForFilter(val) {
18510 return (val === null) ? 'null' : typeof val;
18511}
18512
18513/**
18514 * @ngdoc filter
18515 * @name currency
18516 * @kind function
18517 *
18518 * @description
18519 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18520 * symbol for current locale is used.
18521 *
18522 * @param {number} amount Input to filter.
18523 * @param {string=} symbol Currency symbol or identifier to be displayed.
18524 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18525 * @returns {string} Formatted number.
18526 *
18527 *
18528 * @example
18529 <example module="currencyExample">
18530 <file name="index.html">
18531 <script>
18532 angular.module('currencyExample', [])
18533 .controller('ExampleController', ['$scope', function($scope) {
18534 $scope.amount = 1234.56;
18535 }]);
18536 </script>
18537 <div ng-controller="ExampleController">
18538 <input type="number" ng-model="amount" aria-label="amount"> <br>
18539 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18540 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18541 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18542 </div>
18543 </file>
18544 <file name="protractor.js" type="protractor">
18545 it('should init with 1234.56', function() {
18546 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18547 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18548 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18549 });
18550 it('should update', function() {
18551 if (browser.params.browser == 'safari') {
18552 // Safari does not understand the minus key. See
18553 // https://github.com/angular/protractor/issues/481
18554 return;
18555 }
18556 element(by.model('amount')).clear();
18557 element(by.model('amount')).sendKeys('-1234');
18558 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18559 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18560 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18561 });
18562 </file>
18563 </example>
18564 */
18565currencyFilter.$inject = ['$locale'];
18566function currencyFilter($locale) {
18567 var formats = $locale.NUMBER_FORMATS;
18568 return function(amount, currencySymbol, fractionSize) {
18569 if (isUndefined(currencySymbol)) {
18570 currencySymbol = formats.CURRENCY_SYM;
18571 }
18572
18573 if (isUndefined(fractionSize)) {
18574 fractionSize = formats.PATTERNS[1].maxFrac;
18575 }
18576
18577 // if null or undefined pass it through
18578 return (amount == null)
18579 ? amount
18580 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18581 replace(/\u00A4/g, currencySymbol);
18582 };
18583}
18584
18585/**
18586 * @ngdoc filter
18587 * @name number
18588 * @kind function
18589 *
18590 * @description
18591 * Formats a number as text.
18592 *
18593 * If the input is null or undefined, it will just be returned.
18594 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18595 * If the input is not a number an empty string is returned.
18596 *
18597 *
18598 * @param {number|string} number Number to format.
18599 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18600 * If this is not provided then the fraction size is computed from the current locale's number
18601 * formatting pattern. In the case of the default locale, it will be 3.
18602 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18603 *
18604 * @example
18605 <example module="numberFilterExample">
18606 <file name="index.html">
18607 <script>
18608 angular.module('numberFilterExample', [])
18609 .controller('ExampleController', ['$scope', function($scope) {
18610 $scope.val = 1234.56789;
18611 }]);
18612 </script>
18613 <div ng-controller="ExampleController">
18614 <label>Enter number: <input ng-model='val'></label><br>
18615 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18616 No fractions: <span>{{val | number:0}}</span><br>
18617 Negative number: <span>{{-val | number:4}}</span>
18618 </div>
18619 </file>
18620 <file name="protractor.js" type="protractor">
18621 it('should format numbers', function() {
18622 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18623 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18624 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18625 });
18626
18627 it('should update', function() {
18628 element(by.model('val')).clear();
18629 element(by.model('val')).sendKeys('3374.333');
18630 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18631 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18632 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18633 });
18634 </file>
18635 </example>
18636 */
18637
18638
18639numberFilter.$inject = ['$locale'];
18640function numberFilter($locale) {
18641 var formats = $locale.NUMBER_FORMATS;
18642 return function(number, fractionSize) {
18643
18644 // if null or undefined pass it through
18645 return (number == null)
18646 ? number
18647 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18648 fractionSize);
18649 };
18650}
18651
18652var DECIMAL_SEP = '.';
18653function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18654 if (isObject(number)) return '';
18655
18656 var isNegative = number < 0;
18657 number = Math.abs(number);
18658
18659 var isInfinity = number === Infinity;
18660 if (!isInfinity && !isFinite(number)) return '';
18661
18662 var numStr = number + '',
18663 formatedText = '',
18664 hasExponent = false,
18665 parts = [];
18666
18667 if (isInfinity) formatedText = '\u221e';
18668
18669 if (!isInfinity && numStr.indexOf('e') !== -1) {
18670 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18671 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18672 number = 0;
18673 } else {
18674 formatedText = numStr;
18675 hasExponent = true;
18676 }
18677 }
18678
18679 if (!isInfinity && !hasExponent) {
18680 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18681
18682 // determine fractionSize if it is not specified
18683 if (isUndefined(fractionSize)) {
18684 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18685 }
18686
18687 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18688 // inspired by:
18689 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18690 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18691
18692 var fraction = ('' + number).split(DECIMAL_SEP);
18693 var whole = fraction[0];
18694 fraction = fraction[1] || '';
18695
18696 var i, pos = 0,
18697 lgroup = pattern.lgSize,
18698 group = pattern.gSize;
18699
18700 if (whole.length >= (lgroup + group)) {
18701 pos = whole.length - lgroup;
18702 for (i = 0; i < pos; i++) {
18703 if ((pos - i) % group === 0 && i !== 0) {
18704 formatedText += groupSep;
18705 }
18706 formatedText += whole.charAt(i);
18707 }
18708 }
18709
18710 for (i = pos; i < whole.length; i++) {
18711 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18712 formatedText += groupSep;
18713 }
18714 formatedText += whole.charAt(i);
18715 }
18716
18717 // format fraction part.
18718 while (fraction.length < fractionSize) {
18719 fraction += '0';
18720 }
18721
18722 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18723 } else {
18724 if (fractionSize > 0 && number < 1) {
18725 formatedText = number.toFixed(fractionSize);
18726 number = parseFloat(formatedText);
18727 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18728 }
18729 }
18730
18731 if (number === 0) {
18732 isNegative = false;
18733 }
18734
18735 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18736 formatedText,
18737 isNegative ? pattern.negSuf : pattern.posSuf);
18738 return parts.join('');
18739}
18740
18741function padNumber(num, digits, trim) {
18742 var neg = '';
18743 if (num < 0) {
18744 neg = '-';
18745 num = -num;
18746 }
18747 num = '' + num;
18748 while (num.length < digits) num = '0' + num;
18749 if (trim) {
18750 num = num.substr(num.length - digits);
18751 }
18752 return neg + num;
18753}
18754
18755
18756function dateGetter(name, size, offset, trim) {
18757 offset = offset || 0;
18758 return function(date) {
18759 var value = date['get' + name]();
18760 if (offset > 0 || value > -offset) {
18761 value += offset;
18762 }
18763 if (value === 0 && offset == -12) value = 12;
18764 return padNumber(value, size, trim);
18765 };
18766}
18767
18768function dateStrGetter(name, shortForm) {
18769 return function(date, formats) {
18770 var value = date['get' + name]();
18771 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18772
18773 return formats[get][value];
18774 };
18775}
18776
18777function timeZoneGetter(date, formats, offset) {
18778 var zone = -1 * offset;
18779 var paddedZone = (zone >= 0) ? "+" : "";
18780
18781 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18782 padNumber(Math.abs(zone % 60), 2);
18783
18784 return paddedZone;
18785}
18786
18787function getFirstThursdayOfYear(year) {
18788 // 0 = index of January
18789 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18790 // 4 = index of Thursday (+1 to account for 1st = 5)
18791 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18792 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18793}
18794
18795function getThursdayThisWeek(datetime) {
18796 return new Date(datetime.getFullYear(), datetime.getMonth(),
18797 // 4 = index of Thursday
18798 datetime.getDate() + (4 - datetime.getDay()));
18799}
18800
18801function weekGetter(size) {
18802 return function(date) {
18803 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18804 thisThurs = getThursdayThisWeek(date);
18805
18806 var diff = +thisThurs - +firstThurs,
18807 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18808
18809 return padNumber(result, size);
18810 };
18811}
18812
18813function ampmGetter(date, formats) {
18814 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18815}
18816
18817function eraGetter(date, formats) {
18818 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18819}
18820
18821function longEraGetter(date, formats) {
18822 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18823}
18824
18825var DATE_FORMATS = {
18826 yyyy: dateGetter('FullYear', 4),
18827 yy: dateGetter('FullYear', 2, 0, true),
18828 y: dateGetter('FullYear', 1),
18829 MMMM: dateStrGetter('Month'),
18830 MMM: dateStrGetter('Month', true),
18831 MM: dateGetter('Month', 2, 1),
18832 M: dateGetter('Month', 1, 1),
18833 dd: dateGetter('Date', 2),
18834 d: dateGetter('Date', 1),
18835 HH: dateGetter('Hours', 2),
18836 H: dateGetter('Hours', 1),
18837 hh: dateGetter('Hours', 2, -12),
18838 h: dateGetter('Hours', 1, -12),
18839 mm: dateGetter('Minutes', 2),
18840 m: dateGetter('Minutes', 1),
18841 ss: dateGetter('Seconds', 2),
18842 s: dateGetter('Seconds', 1),
18843 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18844 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18845 sss: dateGetter('Milliseconds', 3),
18846 EEEE: dateStrGetter('Day'),
18847 EEE: dateStrGetter('Day', true),
18848 a: ampmGetter,
18849 Z: timeZoneGetter,
18850 ww: weekGetter(2),
18851 w: weekGetter(1),
18852 G: eraGetter,
18853 GG: eraGetter,
18854 GGG: eraGetter,
18855 GGGG: longEraGetter
18856};
18857
18858var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
18859 NUMBER_STRING = /^\-?\d+$/;
18860
18861/**
18862 * @ngdoc filter
18863 * @name date
18864 * @kind function
18865 *
18866 * @description
18867 * Formats `date` to a string based on the requested `format`.
18868 *
18869 * `format` string can be composed of the following elements:
18870 *
18871 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
18872 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
18873 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
18874 * * `'MMMM'`: Month in year (January-December)
18875 * * `'MMM'`: Month in year (Jan-Dec)
18876 * * `'MM'`: Month in year, padded (01-12)
18877 * * `'M'`: Month in year (1-12)
18878 * * `'dd'`: Day in month, padded (01-31)
18879 * * `'d'`: Day in month (1-31)
18880 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
18881 * * `'EEE'`: Day in Week, (Sun-Sat)
18882 * * `'HH'`: Hour in day, padded (00-23)
18883 * * `'H'`: Hour in day (0-23)
18884 * * `'hh'`: Hour in AM/PM, padded (01-12)
18885 * * `'h'`: Hour in AM/PM, (1-12)
18886 * * `'mm'`: Minute in hour, padded (00-59)
18887 * * `'m'`: Minute in hour (0-59)
18888 * * `'ss'`: Second in minute, padded (00-59)
18889 * * `'s'`: Second in minute (0-59)
18890 * * `'sss'`: Millisecond in second, padded (000-999)
18891 * * `'a'`: AM/PM marker
18892 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
18893 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
18894 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
18895 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
18896 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
18897 *
18898 * `format` string can also be one of the following predefined
18899 * {@link guide/i18n localizable formats}:
18900 *
18901 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
18902 * (e.g. Sep 3, 2010 12:05:08 PM)
18903 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
18904 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
18905 * (e.g. Friday, September 3, 2010)
18906 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
18907 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
18908 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
18909 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
18910 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
18911 *
18912 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
18913 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
18914 * (e.g. `"h 'o''clock'"`).
18915 *
18916 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
18917 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
18918 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
18919 * specified in the string input, the time is considered to be in the local timezone.
18920 * @param {string=} format Formatting rules (see Description). If not specified,
18921 * `mediumDate` is used.
18922 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
18923 * continental US time zone abbreviations, but for general use, use a time zone offset, for
18924 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
18925 * If not specified, the timezone of the browser will be used.
18926 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
18927 *
18928 * @example
18929 <example>
18930 <file name="index.html">
18931 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
18932 <span>{{1288323623006 | date:'medium'}}</span><br>
18933 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
18934 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
18935 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
18936 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
18937 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
18938 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
18939 </file>
18940 <file name="protractor.js" type="protractor">
18941 it('should format date', function() {
18942 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
18943 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
18944 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
18945 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
18946 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
18947 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
18948 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
18949 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
18950 });
18951 </file>
18952 </example>
18953 */
18954dateFilter.$inject = ['$locale'];
18955function dateFilter($locale) {
18956
18957
18958 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
18959 // 1 2 3 4 5 6 7 8 9 10 11
18960 function jsonStringToDate(string) {
18961 var match;
18962 if (match = string.match(R_ISO8601_STR)) {
18963 var date = new Date(0),
18964 tzHour = 0,
18965 tzMin = 0,
18966 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
18967 timeSetter = match[8] ? date.setUTCHours : date.setHours;
18968
18969 if (match[9]) {
18970 tzHour = toInt(match[9] + match[10]);
18971 tzMin = toInt(match[9] + match[11]);
18972 }
18973 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
18974 var h = toInt(match[4] || 0) - tzHour;
18975 var m = toInt(match[5] || 0) - tzMin;
18976 var s = toInt(match[6] || 0);
18977 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
18978 timeSetter.call(date, h, m, s, ms);
18979 return date;
18980 }
18981 return string;
18982 }
18983
18984
18985 return function(date, format, timezone) {
18986 var text = '',
18987 parts = [],
18988 fn, match;
18989
18990 format = format || 'mediumDate';
18991 format = $locale.DATETIME_FORMATS[format] || format;
18992 if (isString(date)) {
18993 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
18994 }
18995
18996 if (isNumber(date)) {
18997 date = new Date(date);
18998 }
18999
19000 if (!isDate(date) || !isFinite(date.getTime())) {
19001 return date;
19002 }
19003
19004 while (format) {
19005 match = DATE_FORMATS_SPLIT.exec(format);
19006 if (match) {
19007 parts = concat(parts, match, 1);
19008 format = parts.pop();
19009 } else {
19010 parts.push(format);
19011 format = null;
19012 }
19013 }
19014
19015 var dateTimezoneOffset = date.getTimezoneOffset();
19016 if (timezone) {
19017 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19018 date = convertTimezoneToLocal(date, timezone, true);
19019 }
19020 forEach(parts, function(value) {
19021 fn = DATE_FORMATS[value];
19022 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19023 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19024 });
19025
19026 return text;
19027 };
19028}
19029
19030
19031/**
19032 * @ngdoc filter
19033 * @name json
19034 * @kind function
19035 *
19036 * @description
19037 * Allows you to convert a JavaScript object into JSON string.
19038 *
19039 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19040 * the binding is automatically converted to JSON.
19041 *
19042 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19043 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19044 * @returns {string} JSON string.
19045 *
19046 *
19047 * @example
19048 <example>
19049 <file name="index.html">
19050 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19051 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19052 </file>
19053 <file name="protractor.js" type="protractor">
19054 it('should jsonify filtered objects', function() {
19055 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19056 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19057 });
19058 </file>
19059 </example>
19060 *
19061 */
19062function jsonFilter() {
19063 return function(object, spacing) {
19064 if (isUndefined(spacing)) {
19065 spacing = 2;
19066 }
19067 return toJson(object, spacing);
19068 };
19069}
19070
19071
19072/**
19073 * @ngdoc filter
19074 * @name lowercase
19075 * @kind function
19076 * @description
19077 * Converts string to lowercase.
19078 * @see angular.lowercase
19079 */
19080var lowercaseFilter = valueFn(lowercase);
19081
19082
19083/**
19084 * @ngdoc filter
19085 * @name uppercase
19086 * @kind function
19087 * @description
19088 * Converts string to uppercase.
19089 * @see angular.uppercase
19090 */
19091var uppercaseFilter = valueFn(uppercase);
19092
19093/**
19094 * @ngdoc filter
19095 * @name limitTo
19096 * @kind function
19097 *
19098 * @description
19099 * Creates a new array or string containing only a specified number of elements. The elements
19100 * are taken from either the beginning or the end of the source array, string or number, as specified by
19101 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19102 * converted to a string.
19103 *
19104 * @param {Array|string|number} input Source array, string or number to be limited.
19105 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19106 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19107 * If the number is negative, `limit` number of items from the end of the source array/string
19108 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19109 * the input will be returned unchanged.
19110 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19111 * indicates an offset from the end of `input`. Defaults to `0`.
19112 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19113 * had less than `limit` elements.
19114 *
19115 * @example
19116 <example module="limitToExample">
19117 <file name="index.html">
19118 <script>
19119 angular.module('limitToExample', [])
19120 .controller('ExampleController', ['$scope', function($scope) {
19121 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19122 $scope.letters = "abcdefghi";
19123 $scope.longNumber = 2345432342;
19124 $scope.numLimit = 3;
19125 $scope.letterLimit = 3;
19126 $scope.longNumberLimit = 3;
19127 }]);
19128 </script>
19129 <div ng-controller="ExampleController">
19130 <label>
19131 Limit {{numbers}} to:
19132 <input type="number" step="1" ng-model="numLimit">
19133 </label>
19134 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19135 <label>
19136 Limit {{letters}} to:
19137 <input type="number" step="1" ng-model="letterLimit">
19138 </label>
19139 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19140 <label>
19141 Limit {{longNumber}} to:
19142 <input type="number" step="1" ng-model="longNumberLimit">
19143 </label>
19144 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19145 </div>
19146 </file>
19147 <file name="protractor.js" type="protractor">
19148 var numLimitInput = element(by.model('numLimit'));
19149 var letterLimitInput = element(by.model('letterLimit'));
19150 var longNumberLimitInput = element(by.model('longNumberLimit'));
19151 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19152 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19153 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19154
19155 it('should limit the number array to first three items', function() {
19156 expect(numLimitInput.getAttribute('value')).toBe('3');
19157 expect(letterLimitInput.getAttribute('value')).toBe('3');
19158 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19159 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19160 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19161 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19162 });
19163
19164 // There is a bug in safari and protractor that doesn't like the minus key
19165 // it('should update the output when -3 is entered', function() {
19166 // numLimitInput.clear();
19167 // numLimitInput.sendKeys('-3');
19168 // letterLimitInput.clear();
19169 // letterLimitInput.sendKeys('-3');
19170 // longNumberLimitInput.clear();
19171 // longNumberLimitInput.sendKeys('-3');
19172 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19173 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19174 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19175 // });
19176
19177 it('should not exceed the maximum size of input array', function() {
19178 numLimitInput.clear();
19179 numLimitInput.sendKeys('100');
19180 letterLimitInput.clear();
19181 letterLimitInput.sendKeys('100');
19182 longNumberLimitInput.clear();
19183 longNumberLimitInput.sendKeys('100');
19184 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19185 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19186 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19187 });
19188 </file>
19189 </example>
19190*/
19191function limitToFilter() {
19192 return function(input, limit, begin) {
19193 if (Math.abs(Number(limit)) === Infinity) {
19194 limit = Number(limit);
19195 } else {
19196 limit = toInt(limit);
19197 }
19198 if (isNaN(limit)) return input;
19199
19200 if (isNumber(input)) input = input.toString();
19201 if (!isArray(input) && !isString(input)) return input;
19202
19203 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19204 begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
19205
19206 if (limit >= 0) {
19207 return input.slice(begin, begin + limit);
19208 } else {
19209 if (begin === 0) {
19210 return input.slice(limit, input.length);
19211 } else {
19212 return input.slice(Math.max(0, begin + limit), begin);
19213 }
19214 }
19215 };
19216}
19217
19218/**
19219 * @ngdoc filter
19220 * @name orderBy
19221 * @kind function
19222 *
19223 * @description
19224 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19225 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19226 * as expected, make sure they are actually being saved as numbers and not strings.
19227 *
19228 * @param {Array} array The array to sort.
19229 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19230 * used by the comparator to determine the order of elements.
19231 *
19232 * Can be one of:
19233 *
19234 * - `function`: Getter function. The result of this function will be sorted using the
19235 * `<`, `===`, `>` operator.
19236 * - `string`: An Angular expression. The result of this expression is used to compare elements
19237 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19238 * 3 first characters of a property called `name`). The result of a constant expression
19239 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19240 * to sort object by the value of their `special name` property). An expression can be
19241 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19242 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19243 * element itself is used to compare where sorting.
19244 * - `Array`: An array of function or string predicates. The first predicate in the array
19245 * is used for sorting, but when two items are equivalent, the next predicate is used.
19246 *
19247 * If the predicate is missing or empty then it defaults to `'+'`.
19248 *
19249 * @param {boolean=} reverse Reverse the order of the array.
19250 * @returns {Array} Sorted copy of the source array.
19251 *
19252 *
19253 * @example
19254 * The example below demonstrates a simple ngRepeat, where the data is sorted
19255 * by age in descending order (predicate is set to `'-age'`).
19256 * `reverse` is not set, which means it defaults to `false`.
19257 <example module="orderByExample">
19258 <file name="index.html">
19259 <script>
19260 angular.module('orderByExample', [])
19261 .controller('ExampleController', ['$scope', function($scope) {
19262 $scope.friends =
19263 [{name:'John', phone:'555-1212', age:10},
19264 {name:'Mary', phone:'555-9876', age:19},
19265 {name:'Mike', phone:'555-4321', age:21},
19266 {name:'Adam', phone:'555-5678', age:35},
19267 {name:'Julie', phone:'555-8765', age:29}];
19268 }]);
19269 </script>
19270 <div ng-controller="ExampleController">
19271 <table class="friend">
19272 <tr>
19273 <th>Name</th>
19274 <th>Phone Number</th>
19275 <th>Age</th>
19276 </tr>
19277 <tr ng-repeat="friend in friends | orderBy:'-age'">
19278 <td>{{friend.name}}</td>
19279 <td>{{friend.phone}}</td>
19280 <td>{{friend.age}}</td>
19281 </tr>
19282 </table>
19283 </div>
19284 </file>
19285 </example>
19286 *
19287 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19288 * as shown in the next example.
19289 * @example
19290 <example module="orderByExample">
19291 <file name="index.html">
19292 <script>
19293 angular.module('orderByExample', [])
19294 .controller('ExampleController', ['$scope', function($scope) {
19295 $scope.friends =
19296 [{name:'John', phone:'555-1212', age:10},
19297 {name:'Mary', phone:'555-9876', age:19},
19298 {name:'Mike', phone:'555-4321', age:21},
19299 {name:'Adam', phone:'555-5678', age:35},
19300 {name:'Julie', phone:'555-8765', age:29}];
19301 $scope.predicate = 'age';
19302 $scope.reverse = true;
19303 $scope.order = function(predicate) {
19304 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19305 $scope.predicate = predicate;
19306 };
19307 }]);
19308 </script>
19309 <style type="text/css">
19310 .sortorder:after {
19311 content: '\25b2';
19312 }
19313 .sortorder.reverse:after {
19314 content: '\25bc';
19315 }
19316 </style>
19317 <div ng-controller="ExampleController">
19318 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19319 <hr/>
19320 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19321 <table class="friend">
19322 <tr>
19323 <th>
19324 <a href="" ng-click="order('name')">Name</a>
19325 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19326 </th>
19327 <th>
19328 <a href="" ng-click="order('phone')">Phone Number</a>
19329 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19330 </th>
19331 <th>
19332 <a href="" ng-click="order('age')">Age</a>
19333 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19334 </th>
19335 </tr>
19336 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19337 <td>{{friend.name}}</td>
19338 <td>{{friend.phone}}</td>
19339 <td>{{friend.age}}</td>
19340 </tr>
19341 </table>
19342 </div>
19343 </file>
19344 </example>
19345 *
19346 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19347 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19348 * desired parameters.
19349 *
19350 * Example:
19351 *
19352 * @example
19353 <example module="orderByExample">
19354 <file name="index.html">
19355 <div ng-controller="ExampleController">
19356 <table class="friend">
19357 <tr>
19358 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19359 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19360 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19361 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19362 </tr>
19363 <tr ng-repeat="friend in friends">
19364 <td>{{friend.name}}</td>
19365 <td>{{friend.phone}}</td>
19366 <td>{{friend.age}}</td>
19367 </tr>
19368 </table>
19369 </div>
19370 </file>
19371
19372 <file name="script.js">
19373 angular.module('orderByExample', [])
19374 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19375 var orderBy = $filter('orderBy');
19376 $scope.friends = [
19377 { name: 'John', phone: '555-1212', age: 10 },
19378 { name: 'Mary', phone: '555-9876', age: 19 },
19379 { name: 'Mike', phone: '555-4321', age: 21 },
19380 { name: 'Adam', phone: '555-5678', age: 35 },
19381 { name: 'Julie', phone: '555-8765', age: 29 }
19382 ];
19383 $scope.order = function(predicate, reverse) {
19384 $scope.friends = orderBy($scope.friends, predicate, reverse);
19385 };
19386 $scope.order('-age',false);
19387 }]);
19388 </file>
19389</example>
19390 */
19391orderByFilter.$inject = ['$parse'];
19392function orderByFilter($parse) {
19393 return function(array, sortPredicate, reverseOrder) {
19394
19395 if (!(isArrayLike(array))) return array;
19396
19397 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19398 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19399
19400 var predicates = processPredicates(sortPredicate, reverseOrder);
19401 // Add a predicate at the end that evaluates to the element index. This makes the
19402 // sort stable as it works as a tie-breaker when all the input predicates cannot
19403 // distinguish between two elements.
19404 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19405
19406 // The next three lines are a version of a Swartzian Transform idiom from Perl
19407 // (sometimes called the Decorate-Sort-Undecorate idiom)
19408 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19409 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19410 compareValues.sort(doComparison);
19411 array = compareValues.map(function(item) { return item.value; });
19412
19413 return array;
19414
19415 function getComparisonObject(value, index) {
19416 return {
19417 value: value,
19418 predicateValues: predicates.map(function(predicate) {
19419 return getPredicateValue(predicate.get(value), index);
19420 })
19421 };
19422 }
19423
19424 function doComparison(v1, v2) {
19425 var result = 0;
19426 for (var index=0, length = predicates.length; index < length; ++index) {
19427 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19428 if (result) break;
19429 }
19430 return result;
19431 }
19432 };
19433
19434 function processPredicates(sortPredicate, reverseOrder) {
19435 reverseOrder = reverseOrder ? -1 : 1;
19436 return sortPredicate.map(function(predicate) {
19437 var descending = 1, get = identity;
19438
19439 if (isFunction(predicate)) {
19440 get = predicate;
19441 } else if (isString(predicate)) {
19442 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19443 descending = predicate.charAt(0) == '-' ? -1 : 1;
19444 predicate = predicate.substring(1);
19445 }
19446 if (predicate !== '') {
19447 get = $parse(predicate);
19448 if (get.constant) {
19449 var key = get();
19450 get = function(value) { return value[key]; };
19451 }
19452 }
19453 }
19454 return { get: get, descending: descending * reverseOrder };
19455 });
19456 }
19457
19458 function isPrimitive(value) {
19459 switch (typeof value) {
19460 case 'number': /* falls through */
19461 case 'boolean': /* falls through */
19462 case 'string':
19463 return true;
19464 default:
19465 return false;
19466 }
19467 }
19468
19469 function objectValue(value, index) {
19470 // If `valueOf` is a valid function use that
19471 if (typeof value.valueOf === 'function') {
19472 value = value.valueOf();
19473 if (isPrimitive(value)) return value;
19474 }
19475 // If `toString` is a valid function and not the one from `Object.prototype` use that
19476 if (hasCustomToString(value)) {
19477 value = value.toString();
19478 if (isPrimitive(value)) return value;
19479 }
19480 // We have a basic object so we use the position of the object in the collection
19481 return index;
19482 }
19483
19484 function getPredicateValue(value, index) {
19485 var type = typeof value;
19486 if (value === null) {
19487 type = 'string';
19488 value = 'null';
19489 } else if (type === 'string') {
19490 value = value.toLowerCase();
19491 } else if (type === 'object') {
19492 value = objectValue(value, index);
19493 }
19494 return { value: value, type: type };
19495 }
19496
19497 function compare(v1, v2) {
19498 var result = 0;
19499 if (v1.type === v2.type) {
19500 if (v1.value !== v2.value) {
19501 result = v1.value < v2.value ? -1 : 1;
19502 }
19503 } else {
19504 result = v1.type < v2.type ? -1 : 1;
19505 }
19506 return result;
19507 }
19508}
19509
19510function ngDirective(directive) {
19511 if (isFunction(directive)) {
19512 directive = {
19513 link: directive
19514 };
19515 }
19516 directive.restrict = directive.restrict || 'AC';
19517 return valueFn(directive);
19518}
19519
19520/**
19521 * @ngdoc directive
19522 * @name a
19523 * @restrict E
19524 *
19525 * @description
19526 * Modifies the default behavior of the html A tag so that the default action is prevented when
19527 * the href attribute is empty.
19528 *
19529 * This change permits the easy creation of action links with the `ngClick` directive
19530 * without changing the location or causing page reloads, e.g.:
19531 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19532 */
19533var htmlAnchorDirective = valueFn({
19534 restrict: 'E',
19535 compile: function(element, attr) {
19536 if (!attr.href && !attr.xlinkHref) {
19537 return function(scope, element) {
19538 // If the linked element is not an anchor tag anymore, do nothing
19539 if (element[0].nodeName.toLowerCase() !== 'a') return;
19540
19541 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19542 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19543 'xlink:href' : 'href';
19544 element.on('click', function(event) {
19545 // if we have no href url, then don't navigate anywhere.
19546 if (!element.attr(href)) {
19547 event.preventDefault();
19548 }
19549 });
19550 };
19551 }
19552 }
19553});
19554
19555/**
19556 * @ngdoc directive
19557 * @name ngHref
19558 * @restrict A
19559 * @priority 99
19560 *
19561 * @description
19562 * Using Angular markup like `{{hash}}` in an href attribute will
19563 * make the link go to the wrong URL if the user clicks it before
19564 * Angular has a chance to replace the `{{hash}}` markup with its
19565 * value. Until Angular replaces the markup the link will be broken
19566 * and will most likely return a 404 error. The `ngHref` directive
19567 * solves this problem.
19568 *
19569 * The wrong way to write it:
19570 * ```html
19571 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19572 * ```
19573 *
19574 * The correct way to write it:
19575 * ```html
19576 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19577 * ```
19578 *
19579 * @element A
19580 * @param {template} ngHref any string which can contain `{{}}` markup.
19581 *
19582 * @example
19583 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19584 * in links and their different behaviors:
19585 <example>
19586 <file name="index.html">
19587 <input ng-model="value" /><br />
19588 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19589 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19590 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19591 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19592 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19593 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19594 </file>
19595 <file name="protractor.js" type="protractor">
19596 it('should execute ng-click but not reload when href without value', function() {
19597 element(by.id('link-1')).click();
19598 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19599 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19600 });
19601
19602 it('should execute ng-click but not reload when href empty string', function() {
19603 element(by.id('link-2')).click();
19604 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19605 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19606 });
19607
19608 it('should execute ng-click and change url when ng-href specified', function() {
19609 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19610
19611 element(by.id('link-3')).click();
19612
19613 // At this point, we navigate away from an Angular page, so we need
19614 // to use browser.driver to get the base webdriver.
19615
19616 browser.wait(function() {
19617 return browser.driver.getCurrentUrl().then(function(url) {
19618 return url.match(/\/123$/);
19619 });
19620 }, 5000, 'page should navigate to /123');
19621 });
19622
19623 it('should execute ng-click but not reload when href empty string and name specified', function() {
19624 element(by.id('link-4')).click();
19625 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19626 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19627 });
19628
19629 it('should execute ng-click but not reload when no href but name specified', function() {
19630 element(by.id('link-5')).click();
19631 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19632 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19633 });
19634
19635 it('should only change url when only ng-href', function() {
19636 element(by.model('value')).clear();
19637 element(by.model('value')).sendKeys('6');
19638 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19639
19640 element(by.id('link-6')).click();
19641
19642 // At this point, we navigate away from an Angular page, so we need
19643 // to use browser.driver to get the base webdriver.
19644 browser.wait(function() {
19645 return browser.driver.getCurrentUrl().then(function(url) {
19646 return url.match(/\/6$/);
19647 });
19648 }, 5000, 'page should navigate to /6');
19649 });
19650 </file>
19651 </example>
19652 */
19653
19654/**
19655 * @ngdoc directive
19656 * @name ngSrc
19657 * @restrict A
19658 * @priority 99
19659 *
19660 * @description
19661 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19662 * work right: The browser will fetch from the URL with the literal
19663 * text `{{hash}}` until Angular replaces the expression inside
19664 * `{{hash}}`. The `ngSrc` directive solves this problem.
19665 *
19666 * The buggy way to write it:
19667 * ```html
19668 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19669 * ```
19670 *
19671 * The correct way to write it:
19672 * ```html
19673 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19674 * ```
19675 *
19676 * @element IMG
19677 * @param {template} ngSrc any string which can contain `{{}}` markup.
19678 */
19679
19680/**
19681 * @ngdoc directive
19682 * @name ngSrcset
19683 * @restrict A
19684 * @priority 99
19685 *
19686 * @description
19687 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19688 * work right: The browser will fetch from the URL with the literal
19689 * text `{{hash}}` until Angular replaces the expression inside
19690 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19691 *
19692 * The buggy way to write it:
19693 * ```html
19694 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19695 * ```
19696 *
19697 * The correct way to write it:
19698 * ```html
19699 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19700 * ```
19701 *
19702 * @element IMG
19703 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19704 */
19705
19706/**
19707 * @ngdoc directive
19708 * @name ngDisabled
19709 * @restrict A
19710 * @priority 100
19711 *
19712 * @description
19713 *
19714 * This directive sets the `disabled` attribute on the element if the
19715 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19716 *
19717 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19718 * attribute. The following example would make the button enabled on Chrome/Firefox
19719 * but not on older IEs:
19720 *
19721 * ```html
19722 * <!-- See below for an example of ng-disabled being used correctly -->
19723 * <div ng-init="isDisabled = false">
19724 * <button disabled="{{isDisabled}}">Disabled</button>
19725 * </div>
19726 * ```
19727 *
19728 * This is because the HTML specification does not require browsers to preserve the values of
19729 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19730 * If we put an Angular interpolation expression into such an attribute then the
19731 * binding information would be lost when the browser removes the attribute.
19732 *
19733 * @example
19734 <example>
19735 <file name="index.html">
19736 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19737 <button ng-model="button" ng-disabled="checked">Button</button>
19738 </file>
19739 <file name="protractor.js" type="protractor">
19740 it('should toggle button', function() {
19741 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19742 element(by.model('checked')).click();
19743 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19744 });
19745 </file>
19746 </example>
19747 *
19748 * @element INPUT
19749 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19750 * then the `disabled` attribute will be set on the element
19751 */
19752
19753
19754/**
19755 * @ngdoc directive
19756 * @name ngChecked
19757 * @restrict A
19758 * @priority 100
19759 *
19760 * @description
19761 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19762 *
19763 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19764 * as this can lead to unexpected behavior.
19765 *
19766 * ### Why do we need `ngChecked`?
19767 *
19768 * The HTML specification does not require browsers to preserve the values of boolean attributes
19769 * such as checked. (Their presence means true and their absence means false.)
19770 * If we put an Angular interpolation expression into such an attribute then the
19771 * binding information would be lost when the browser removes the attribute.
19772 * The `ngChecked` directive solves this problem for the `checked` attribute.
19773 * This complementary directive is not removed by the browser and so provides
19774 * a permanent reliable place to store the binding information.
19775 * @example
19776 <example>
19777 <file name="index.html">
19778 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19779 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19780 </file>
19781 <file name="protractor.js" type="protractor">
19782 it('should check both checkBoxes', function() {
19783 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19784 element(by.model('master')).click();
19785 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19786 });
19787 </file>
19788 </example>
19789 *
19790 * @element INPUT
19791 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19792 * then the `checked` attribute will be set on the element
19793 */
19794
19795
19796/**
19797 * @ngdoc directive
19798 * @name ngReadonly
19799 * @restrict A
19800 * @priority 100
19801 *
19802 * @description
19803 * The HTML specification does not require browsers to preserve the values of boolean attributes
19804 * such as readonly. (Their presence means true and their absence means false.)
19805 * If we put an Angular interpolation expression into such an attribute then the
19806 * binding information would be lost when the browser removes the attribute.
19807 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19808 * This complementary directive is not removed by the browser and so provides
19809 * a permanent reliable place to store the binding information.
19810 * @example
19811 <example>
19812 <file name="index.html">
19813 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19814 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19815 </file>
19816 <file name="protractor.js" type="protractor">
19817 it('should toggle readonly attr', function() {
19818 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19819 element(by.model('checked')).click();
19820 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19821 });
19822 </file>
19823 </example>
19824 *
19825 * @element INPUT
19826 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19827 * then special attribute "readonly" will be set on the element
19828 */
19829
19830
19831/**
19832 * @ngdoc directive
19833 * @name ngSelected
19834 * @restrict A
19835 * @priority 100
19836 *
19837 * @description
19838 * The HTML specification does not require browsers to preserve the values of boolean attributes
19839 * such as selected. (Their presence means true and their absence means false.)
19840 * If we put an Angular interpolation expression into such an attribute then the
19841 * binding information would be lost when the browser removes the attribute.
19842 * The `ngSelected` directive solves this problem for the `selected` attribute.
19843 * This complementary directive is not removed by the browser and so provides
19844 * a permanent reliable place to store the binding information.
19845 *
19846 * @example
19847 <example>
19848 <file name="index.html">
19849 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19850 <select aria-label="ngSelected demo">
19851 <option>Hello!</option>
19852 <option id="greet" ng-selected="selected">Greetings!</option>
19853 </select>
19854 </file>
19855 <file name="protractor.js" type="protractor">
19856 it('should select Greetings!', function() {
19857 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19858 element(by.model('selected')).click();
19859 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
19860 });
19861 </file>
19862 </example>
19863 *
19864 * @element OPTION
19865 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
19866 * then special attribute "selected" will be set on the element
19867 */
19868
19869/**
19870 * @ngdoc directive
19871 * @name ngOpen
19872 * @restrict A
19873 * @priority 100
19874 *
19875 * @description
19876 * The HTML specification does not require browsers to preserve the values of boolean attributes
19877 * such as open. (Their presence means true and their absence means false.)
19878 * If we put an Angular interpolation expression into such an attribute then the
19879 * binding information would be lost when the browser removes the attribute.
19880 * The `ngOpen` directive solves this problem for the `open` attribute.
19881 * This complementary directive is not removed by the browser and so provides
19882 * a permanent reliable place to store the binding information.
19883 * @example
19884 <example>
19885 <file name="index.html">
19886 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
19887 <details id="details" ng-open="open">
19888 <summary>Show/Hide me</summary>
19889 </details>
19890 </file>
19891 <file name="protractor.js" type="protractor">
19892 it('should toggle open', function() {
19893 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
19894 element(by.model('open')).click();
19895 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
19896 });
19897 </file>
19898 </example>
19899 *
19900 * @element DETAILS
19901 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
19902 * then special attribute "open" will be set on the element
19903 */
19904
19905var ngAttributeAliasDirectives = {};
19906
19907// boolean attrs are evaluated
19908forEach(BOOLEAN_ATTR, function(propName, attrName) {
19909 // binding to multiple is not supported
19910 if (propName == "multiple") return;
19911
19912 function defaultLinkFn(scope, element, attr) {
19913 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
19914 attr.$set(attrName, !!value);
19915 });
19916 }
19917
19918 var normalized = directiveNormalize('ng-' + attrName);
19919 var linkFn = defaultLinkFn;
19920
19921 if (propName === 'checked') {
19922 linkFn = function(scope, element, attr) {
19923 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
19924 if (attr.ngModel !== attr[normalized]) {
19925 defaultLinkFn(scope, element, attr);
19926 }
19927 };
19928 }
19929
19930 ngAttributeAliasDirectives[normalized] = function() {
19931 return {
19932 restrict: 'A',
19933 priority: 100,
19934 link: linkFn
19935 };
19936 };
19937});
19938
19939// aliased input attrs are evaluated
19940forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
19941 ngAttributeAliasDirectives[ngAttr] = function() {
19942 return {
19943 priority: 100,
19944 link: function(scope, element, attr) {
19945 //special case ngPattern when a literal regular expression value
19946 //is used as the expression (this way we don't have to watch anything).
19947 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
19948 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
19949 if (match) {
19950 attr.$set("ngPattern", new RegExp(match[1], match[2]));
19951 return;
19952 }
19953 }
19954
19955 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
19956 attr.$set(ngAttr, value);
19957 });
19958 }
19959 };
19960 };
19961});
19962
19963// ng-src, ng-srcset, ng-href are interpolated
19964forEach(['src', 'srcset', 'href'], function(attrName) {
19965 var normalized = directiveNormalize('ng-' + attrName);
19966 ngAttributeAliasDirectives[normalized] = function() {
19967 return {
19968 priority: 99, // it needs to run after the attributes are interpolated
19969 link: function(scope, element, attr) {
19970 var propName = attrName,
19971 name = attrName;
19972
19973 if (attrName === 'href' &&
19974 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
19975 name = 'xlinkHref';
19976 attr.$attr[name] = 'xlink:href';
19977 propName = null;
19978 }
19979
19980 attr.$observe(normalized, function(value) {
19981 if (!value) {
19982 if (attrName === 'href') {
19983 attr.$set(name, null);
19984 }
19985 return;
19986 }
19987
19988 attr.$set(name, value);
19989
19990 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
19991 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
19992 // to set the property as well to achieve the desired effect.
19993 // we use attr[attrName] value since $set can sanitize the url.
19994 if (msie && propName) element.prop(propName, attr[name]);
19995 });
19996 }
19997 };
19998 };
19999});
20000
20001/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20002 */
20003var nullFormCtrl = {
20004 $addControl: noop,
20005 $$renameControl: nullFormRenameControl,
20006 $removeControl: noop,
20007 $setValidity: noop,
20008 $setDirty: noop,
20009 $setPristine: noop,
20010 $setSubmitted: noop
20011},
20012SUBMITTED_CLASS = 'ng-submitted';
20013
20014function nullFormRenameControl(control, name) {
20015 control.$name = name;
20016}
20017
20018/**
20019 * @ngdoc type
20020 * @name form.FormController
20021 *
20022 * @property {boolean} $pristine True if user has not interacted with the form yet.
20023 * @property {boolean} $dirty True if user has already interacted with the form.
20024 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20025 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20026 * @property {boolean} $pending True if at least one containing control or form is pending.
20027 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20028 *
20029 * @property {Object} $error Is an object hash, containing references to controls or
20030 * forms with failing validators, where:
20031 *
20032 * - keys are validation tokens (error names),
20033 * - values are arrays of controls or forms that have a failing validator for given error name.
20034 *
20035 * Built-in validation tokens:
20036 *
20037 * - `email`
20038 * - `max`
20039 * - `maxlength`
20040 * - `min`
20041 * - `minlength`
20042 * - `number`
20043 * - `pattern`
20044 * - `required`
20045 * - `url`
20046 * - `date`
20047 * - `datetimelocal`
20048 * - `time`
20049 * - `week`
20050 * - `month`
20051 *
20052 * @description
20053 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20054 * such as being valid/invalid or dirty/pristine.
20055 *
20056 * Each {@link ng.directive:form form} directive creates an instance
20057 * of `FormController`.
20058 *
20059 */
20060//asks for $scope to fool the BC controller module
20061FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20062function FormController(element, attrs, $scope, $animate, $interpolate) {
20063 var form = this,
20064 controls = [];
20065
20066 // init state
20067 form.$error = {};
20068 form.$$success = {};
20069 form.$pending = undefined;
20070 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20071 form.$dirty = false;
20072 form.$pristine = true;
20073 form.$valid = true;
20074 form.$invalid = false;
20075 form.$submitted = false;
20076 form.$$parentForm = nullFormCtrl;
20077
20078 /**
20079 * @ngdoc method
20080 * @name form.FormController#$rollbackViewValue
20081 *
20082 * @description
20083 * Rollback all form controls pending updates to the `$modelValue`.
20084 *
20085 * Updates may be pending by a debounced event or because the input is waiting for a some future
20086 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20087 * a form that uses `ng-model-options` to pend updates.
20088 */
20089 form.$rollbackViewValue = function() {
20090 forEach(controls, function(control) {
20091 control.$rollbackViewValue();
20092 });
20093 };
20094
20095 /**
20096 * @ngdoc method
20097 * @name form.FormController#$commitViewValue
20098 *
20099 * @description
20100 * Commit all form controls pending updates to the `$modelValue`.
20101 *
20102 * Updates may be pending by a debounced event or because the input is waiting for a some future
20103 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20104 * usually handles calling this in response to input events.
20105 */
20106 form.$commitViewValue = function() {
20107 forEach(controls, function(control) {
20108 control.$commitViewValue();
20109 });
20110 };
20111
20112 /**
20113 * @ngdoc method
20114 * @name form.FormController#$addControl
20115 * @param {object} control control object, either a {@link form.FormController} or an
20116 * {@link ngModel.NgModelController}
20117 *
20118 * @description
20119 * Register a control with the form. Input elements using ngModelController do this automatically
20120 * when they are linked.
20121 *
20122 * Note that the current state of the control will not be reflected on the new parent form. This
20123 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20124 * state.
20125 *
20126 * However, if the method is used programmatically, for example by adding dynamically created controls,
20127 * or controls that have been previously removed without destroying their corresponding DOM element,
20128 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20129 *
20130 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20131 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20132 */
20133 form.$addControl = function(control) {
20134 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20135 // and not added to the scope. Now we throw an error.
20136 assertNotHasOwnProperty(control.$name, 'input');
20137 controls.push(control);
20138
20139 if (control.$name) {
20140 form[control.$name] = control;
20141 }
20142
20143 control.$$parentForm = form;
20144 };
20145
20146 // Private API: rename a form control
20147 form.$$renameControl = function(control, newName) {
20148 var oldName = control.$name;
20149
20150 if (form[oldName] === control) {
20151 delete form[oldName];
20152 }
20153 form[newName] = control;
20154 control.$name = newName;
20155 };
20156
20157 /**
20158 * @ngdoc method
20159 * @name form.FormController#$removeControl
20160 * @param {object} control control object, either a {@link form.FormController} or an
20161 * {@link ngModel.NgModelController}
20162 *
20163 * @description
20164 * Deregister a control from the form.
20165 *
20166 * Input elements using ngModelController do this automatically when they are destroyed.
20167 *
20168 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20169 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20170 * different from case to case. For example, removing the only `$dirty` control from a form may or
20171 * may not mean that the form is still `$dirty`.
20172 */
20173 form.$removeControl = function(control) {
20174 if (control.$name && form[control.$name] === control) {
20175 delete form[control.$name];
20176 }
20177 forEach(form.$pending, function(value, name) {
20178 form.$setValidity(name, null, control);
20179 });
20180 forEach(form.$error, function(value, name) {
20181 form.$setValidity(name, null, control);
20182 });
20183 forEach(form.$$success, function(value, name) {
20184 form.$setValidity(name, null, control);
20185 });
20186
20187 arrayRemove(controls, control);
20188 control.$$parentForm = nullFormCtrl;
20189 };
20190
20191
20192 /**
20193 * @ngdoc method
20194 * @name form.FormController#$setValidity
20195 *
20196 * @description
20197 * Sets the validity of a form control.
20198 *
20199 * This method will also propagate to parent forms.
20200 */
20201 addSetValidityMethod({
20202 ctrl: this,
20203 $element: element,
20204 set: function(object, property, controller) {
20205 var list = object[property];
20206 if (!list) {
20207 object[property] = [controller];
20208 } else {
20209 var index = list.indexOf(controller);
20210 if (index === -1) {
20211 list.push(controller);
20212 }
20213 }
20214 },
20215 unset: function(object, property, controller) {
20216 var list = object[property];
20217 if (!list) {
20218 return;
20219 }
20220 arrayRemove(list, controller);
20221 if (list.length === 0) {
20222 delete object[property];
20223 }
20224 },
20225 $animate: $animate
20226 });
20227
20228 /**
20229 * @ngdoc method
20230 * @name form.FormController#$setDirty
20231 *
20232 * @description
20233 * Sets the form to a dirty state.
20234 *
20235 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20236 * state (ng-dirty class). This method will also propagate to parent forms.
20237 */
20238 form.$setDirty = function() {
20239 $animate.removeClass(element, PRISTINE_CLASS);
20240 $animate.addClass(element, DIRTY_CLASS);
20241 form.$dirty = true;
20242 form.$pristine = false;
20243 form.$$parentForm.$setDirty();
20244 };
20245
20246 /**
20247 * @ngdoc method
20248 * @name form.FormController#$setPristine
20249 *
20250 * @description
20251 * Sets the form to its pristine state.
20252 *
20253 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20254 * state (ng-pristine class). This method will also propagate to all the controls contained
20255 * in this form.
20256 *
20257 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20258 * saving or resetting it.
20259 */
20260 form.$setPristine = function() {
20261 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20262 form.$dirty = false;
20263 form.$pristine = true;
20264 form.$submitted = false;
20265 forEach(controls, function(control) {
20266 control.$setPristine();
20267 });
20268 };
20269
20270 /**
20271 * @ngdoc method
20272 * @name form.FormController#$setUntouched
20273 *
20274 * @description
20275 * Sets the form to its untouched state.
20276 *
20277 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20278 * untouched state (ng-untouched class).
20279 *
20280 * Setting a form controls back to their untouched state is often useful when setting the form
20281 * back to its pristine state.
20282 */
20283 form.$setUntouched = function() {
20284 forEach(controls, function(control) {
20285 control.$setUntouched();
20286 });
20287 };
20288
20289 /**
20290 * @ngdoc method
20291 * @name form.FormController#$setSubmitted
20292 *
20293 * @description
20294 * Sets the form to its submitted state.
20295 */
20296 form.$setSubmitted = function() {
20297 $animate.addClass(element, SUBMITTED_CLASS);
20298 form.$submitted = true;
20299 form.$$parentForm.$setSubmitted();
20300 };
20301}
20302
20303/**
20304 * @ngdoc directive
20305 * @name ngForm
20306 * @restrict EAC
20307 *
20308 * @description
20309 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20310 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20311 * sub-group of controls needs to be determined.
20312 *
20313 * Note: the purpose of `ngForm` is to group controls,
20314 * but not to be a replacement for the `<form>` tag with all of its capabilities
20315 * (e.g. posting to the server, ...).
20316 *
20317 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20318 * related scope, under this name.
20319 *
20320 */
20321
20322 /**
20323 * @ngdoc directive
20324 * @name form
20325 * @restrict E
20326 *
20327 * @description
20328 * Directive that instantiates
20329 * {@link form.FormController FormController}.
20330 *
20331 * If the `name` attribute is specified, the form controller is published onto the current scope under
20332 * this name.
20333 *
20334 * # Alias: {@link ng.directive:ngForm `ngForm`}
20335 *
20336 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20337 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20338 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20339 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20340 * using Angular validation directives in forms that are dynamically generated using the
20341 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20342 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20343 * `ngForm` directive and nest these in an outer `form` element.
20344 *
20345 *
20346 * # CSS classes
20347 * - `ng-valid` is set if the form is valid.
20348 * - `ng-invalid` is set if the form is invalid.
20349 * - `ng-pending` is set if the form is pending.
20350 * - `ng-pristine` is set if the form is pristine.
20351 * - `ng-dirty` is set if the form is dirty.
20352 * - `ng-submitted` is set if the form was submitted.
20353 *
20354 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20355 *
20356 *
20357 * # Submitting a form and preventing the default action
20358 *
20359 * Since the role of forms in client-side Angular applications is different than in classical
20360 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20361 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20362 * to handle the form submission in an application-specific way.
20363 *
20364 * For this reason, Angular prevents the default action (form submission to the server) unless the
20365 * `<form>` element has an `action` attribute specified.
20366 *
20367 * You can use one of the following two ways to specify what javascript method should be called when
20368 * a form is submitted:
20369 *
20370 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20371 * - {@link ng.directive:ngClick ngClick} directive on the first
20372 * button or input field of type submit (input[type=submit])
20373 *
20374 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20375 * or {@link ng.directive:ngClick ngClick} directives.
20376 * This is because of the following form submission rules in the HTML specification:
20377 *
20378 * - If a form has only one input field then hitting enter in this field triggers form submit
20379 * (`ngSubmit`)
20380 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20381 * doesn't trigger submit
20382 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20383 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20384 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20385 *
20386 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20387 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20388 * to have access to the updated model.
20389 *
20390 * ## Animation Hooks
20391 *
20392 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20393 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20394 * other validations that are performed within the form. Animations in ngForm are similar to how
20395 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20396 * as JS animations.
20397 *
20398 * The following example shows a simple way to utilize CSS transitions to style a form element
20399 * that has been rendered as invalid after it has been validated:
20400 *
20401 * <pre>
20402 * //be sure to include ngAnimate as a module to hook into more
20403 * //advanced animations
20404 * .my-form {
20405 * transition:0.5s linear all;
20406 * background: white;
20407 * }
20408 * .my-form.ng-invalid {
20409 * background: red;
20410 * color:white;
20411 * }
20412 * </pre>
20413 *
20414 * @example
20415 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20416 <file name="index.html">
20417 <script>
20418 angular.module('formExample', [])
20419 .controller('FormController', ['$scope', function($scope) {
20420 $scope.userType = 'guest';
20421 }]);
20422 </script>
20423 <style>
20424 .my-form {
20425 transition:all linear 0.5s;
20426 background: transparent;
20427 }
20428 .my-form.ng-invalid {
20429 background: red;
20430 }
20431 </style>
20432 <form name="myForm" ng-controller="FormController" class="my-form">
20433 userType: <input name="input" ng-model="userType" required>
20434 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20435 <code>userType = {{userType}}</code><br>
20436 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20437 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20438 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20439 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20440 </form>
20441 </file>
20442 <file name="protractor.js" type="protractor">
20443 it('should initialize to model', function() {
20444 var userType = element(by.binding('userType'));
20445 var valid = element(by.binding('myForm.input.$valid'));
20446
20447 expect(userType.getText()).toContain('guest');
20448 expect(valid.getText()).toContain('true');
20449 });
20450
20451 it('should be invalid if empty', function() {
20452 var userType = element(by.binding('userType'));
20453 var valid = element(by.binding('myForm.input.$valid'));
20454 var userInput = element(by.model('userType'));
20455
20456 userInput.clear();
20457 userInput.sendKeys('');
20458
20459 expect(userType.getText()).toEqual('userType =');
20460 expect(valid.getText()).toContain('false');
20461 });
20462 </file>
20463 </example>
20464 *
20465 * @param {string=} name Name of the form. If specified, the form controller will be published into
20466 * related scope, under this name.
20467 */
20468var formDirectiveFactory = function(isNgForm) {
20469 return ['$timeout', '$parse', function($timeout, $parse) {
20470 var formDirective = {
20471 name: 'form',
20472 restrict: isNgForm ? 'EAC' : 'E',
20473 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20474 controller: FormController,
20475 compile: function ngFormCompile(formElement, attr) {
20476 // Setup initial state of the control
20477 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20478
20479 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20480
20481 return {
20482 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20483 var controller = ctrls[0];
20484
20485 // if `action` attr is not present on the form, prevent the default action (submission)
20486 if (!('action' in attr)) {
20487 // we can't use jq events because if a form is destroyed during submission the default
20488 // action is not prevented. see #1238
20489 //
20490 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20491 // page reload if the form was destroyed by submission of the form via a click handler
20492 // on a button in the form. Looks like an IE9 specific bug.
20493 var handleFormSubmission = function(event) {
20494 scope.$apply(function() {
20495 controller.$commitViewValue();
20496 controller.$setSubmitted();
20497 });
20498
20499 event.preventDefault();
20500 };
20501
20502 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20503
20504 // unregister the preventDefault listener so that we don't not leak memory but in a
20505 // way that will achieve the prevention of the default action.
20506 formElement.on('$destroy', function() {
20507 $timeout(function() {
20508 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20509 }, 0, false);
20510 });
20511 }
20512
20513 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20514 parentFormCtrl.$addControl(controller);
20515
20516 var setter = nameAttr ? getSetter(controller.$name) : noop;
20517
20518 if (nameAttr) {
20519 setter(scope, controller);
20520 attr.$observe(nameAttr, function(newValue) {
20521 if (controller.$name === newValue) return;
20522 setter(scope, undefined);
20523 controller.$$parentForm.$$renameControl(controller, newValue);
20524 setter = getSetter(controller.$name);
20525 setter(scope, controller);
20526 });
20527 }
20528 formElement.on('$destroy', function() {
20529 controller.$$parentForm.$removeControl(controller);
20530 setter(scope, undefined);
20531 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20532 });
20533 }
20534 };
20535 }
20536 };
20537
20538 return formDirective;
20539
20540 function getSetter(expression) {
20541 if (expression === '') {
20542 //create an assignable expression, so forms with an empty name can be renamed later
20543 return $parse('this[""]').assign;
20544 }
20545 return $parse(expression).assign || noop;
20546 }
20547 }];
20548};
20549
20550var formDirective = formDirectiveFactory();
20551var ngFormDirective = formDirectiveFactory(true);
20552
20553/* global VALID_CLASS: false,
20554 INVALID_CLASS: false,
20555 PRISTINE_CLASS: false,
20556 DIRTY_CLASS: false,
20557 UNTOUCHED_CLASS: false,
20558 TOUCHED_CLASS: false,
20559 ngModelMinErr: false,
20560*/
20561
20562// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20563var 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)/;
20564var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
20565var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
20566var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20567var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20568var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20569var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20570var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20571var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20572
20573var inputType = {
20574
20575 /**
20576 * @ngdoc input
20577 * @name input[text]
20578 *
20579 * @description
20580 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20581 *
20582 *
20583 * @param {string} ngModel Assignable angular expression to data-bind to.
20584 * @param {string=} name Property name of the form under which the control is published.
20585 * @param {string=} required Adds `required` validation error key if the value is not entered.
20586 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20587 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20588 * `required` when you want to data-bind to the `required` attribute.
20589 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20590 * minlength.
20591 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20592 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20593 * any length.
20594 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20595 * that contains the regular expression body that will be converted to a regular expression
20596 * as in the ngPattern directive.
20597 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20598 * a RegExp found by evaluating the Angular expression given in the attribute value.
20599 * If the expression evaluates to a RegExp object, then this is used directly.
20600 * If the expression evaluates to a string, then it will be converted to a RegExp
20601 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20602 * `new RegExp('^abc$')`.<br />
20603 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20604 * start at the index of the last search's match, thus not taking the whole input value into
20605 * account.
20606 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20607 * interaction with the input element.
20608 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20609 * This parameter is ignored for input[type=password] controls, which will never trim the
20610 * input.
20611 *
20612 * @example
20613 <example name="text-input-directive" module="textInputExample">
20614 <file name="index.html">
20615 <script>
20616 angular.module('textInputExample', [])
20617 .controller('ExampleController', ['$scope', function($scope) {
20618 $scope.example = {
20619 text: 'guest',
20620 word: /^\s*\w*\s*$/
20621 };
20622 }]);
20623 </script>
20624 <form name="myForm" ng-controller="ExampleController">
20625 <label>Single word:
20626 <input type="text" name="input" ng-model="example.text"
20627 ng-pattern="example.word" required ng-trim="false">
20628 </label>
20629 <div role="alert">
20630 <span class="error" ng-show="myForm.input.$error.required">
20631 Required!</span>
20632 <span class="error" ng-show="myForm.input.$error.pattern">
20633 Single word only!</span>
20634 </div>
20635 <tt>text = {{example.text}}</tt><br/>
20636 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20637 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20638 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20639 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20640 </form>
20641 </file>
20642 <file name="protractor.js" type="protractor">
20643 var text = element(by.binding('example.text'));
20644 var valid = element(by.binding('myForm.input.$valid'));
20645 var input = element(by.model('example.text'));
20646
20647 it('should initialize to model', function() {
20648 expect(text.getText()).toContain('guest');
20649 expect(valid.getText()).toContain('true');
20650 });
20651
20652 it('should be invalid if empty', function() {
20653 input.clear();
20654 input.sendKeys('');
20655
20656 expect(text.getText()).toEqual('text =');
20657 expect(valid.getText()).toContain('false');
20658 });
20659
20660 it('should be invalid if multi word', function() {
20661 input.clear();
20662 input.sendKeys('hello world');
20663
20664 expect(valid.getText()).toContain('false');
20665 });
20666 </file>
20667 </example>
20668 */
20669 'text': textInputType,
20670
20671 /**
20672 * @ngdoc input
20673 * @name input[date]
20674 *
20675 * @description
20676 * Input with date validation and transformation. In browsers that do not yet support
20677 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20678 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20679 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20680 * expected input format via a placeholder or label.
20681 *
20682 * The model must always be a Date object, otherwise Angular will throw an error.
20683 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20684 *
20685 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20686 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20687 *
20688 * @param {string} ngModel Assignable angular expression to data-bind to.
20689 * @param {string=} name Property name of the form under which the control is published.
20690 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20691 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20692 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20693 * constraint validation.
20694 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20695 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20696 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20697 * constraint validation.
20698 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20699 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20700 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20701 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20702 * @param {string=} required Sets `required` validation error key if the value is not entered.
20703 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20704 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20705 * `required` when you want to data-bind to the `required` attribute.
20706 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20707 * interaction with the input element.
20708 *
20709 * @example
20710 <example name="date-input-directive" module="dateInputExample">
20711 <file name="index.html">
20712 <script>
20713 angular.module('dateInputExample', [])
20714 .controller('DateController', ['$scope', function($scope) {
20715 $scope.example = {
20716 value: new Date(2013, 9, 22)
20717 };
20718 }]);
20719 </script>
20720 <form name="myForm" ng-controller="DateController as dateCtrl">
20721 <label for="exampleInput">Pick a date in 2013:</label>
20722 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20723 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20724 <div role="alert">
20725 <span class="error" ng-show="myForm.input.$error.required">
20726 Required!</span>
20727 <span class="error" ng-show="myForm.input.$error.date">
20728 Not a valid date!</span>
20729 </div>
20730 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20731 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20732 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20733 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20734 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20735 </form>
20736 </file>
20737 <file name="protractor.js" type="protractor">
20738 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20739 var valid = element(by.binding('myForm.input.$valid'));
20740 var input = element(by.model('example.value'));
20741
20742 // currently protractor/webdriver does not support
20743 // sending keys to all known HTML5 input controls
20744 // for various browsers (see https://github.com/angular/protractor/issues/562).
20745 function setInput(val) {
20746 // set the value of the element and force validation.
20747 var scr = "var ipt = document.getElementById('exampleInput'); " +
20748 "ipt.value = '" + val + "';" +
20749 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20750 browser.executeScript(scr);
20751 }
20752
20753 it('should initialize to model', function() {
20754 expect(value.getText()).toContain('2013-10-22');
20755 expect(valid.getText()).toContain('myForm.input.$valid = true');
20756 });
20757
20758 it('should be invalid if empty', function() {
20759 setInput('');
20760 expect(value.getText()).toEqual('value =');
20761 expect(valid.getText()).toContain('myForm.input.$valid = false');
20762 });
20763
20764 it('should be invalid if over max', function() {
20765 setInput('2015-01-01');
20766 expect(value.getText()).toContain('');
20767 expect(valid.getText()).toContain('myForm.input.$valid = false');
20768 });
20769 </file>
20770 </example>
20771 */
20772 'date': createDateInputType('date', DATE_REGEXP,
20773 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20774 'yyyy-MM-dd'),
20775
20776 /**
20777 * @ngdoc input
20778 * @name input[datetime-local]
20779 *
20780 * @description
20781 * Input with datetime validation and transformation. In browsers that do not yet support
20782 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20783 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20784 *
20785 * The model must always be a Date object, otherwise Angular will throw an error.
20786 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20787 *
20788 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20789 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20790 *
20791 * @param {string} ngModel Assignable angular expression to data-bind to.
20792 * @param {string=} name Property name of the form under which the control is published.
20793 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20794 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20795 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20796 * Note that `min` will also add native HTML5 constraint validation.
20797 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20798 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20799 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20800 * Note that `max` will also add native HTML5 constraint validation.
20801 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20802 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20803 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20804 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20805 * @param {string=} required Sets `required` validation error key if the value is not entered.
20806 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20807 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20808 * `required` when you want to data-bind to the `required` attribute.
20809 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20810 * interaction with the input element.
20811 *
20812 * @example
20813 <example name="datetimelocal-input-directive" module="dateExample">
20814 <file name="index.html">
20815 <script>
20816 angular.module('dateExample', [])
20817 .controller('DateController', ['$scope', function($scope) {
20818 $scope.example = {
20819 value: new Date(2010, 11, 28, 14, 57)
20820 };
20821 }]);
20822 </script>
20823 <form name="myForm" ng-controller="DateController as dateCtrl">
20824 <label for="exampleInput">Pick a date between in 2013:</label>
20825 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20826 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20827 <div role="alert">
20828 <span class="error" ng-show="myForm.input.$error.required">
20829 Required!</span>
20830 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20831 Not a valid date!</span>
20832 </div>
20833 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20834 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20835 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20836 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20837 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20838 </form>
20839 </file>
20840 <file name="protractor.js" type="protractor">
20841 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20842 var valid = element(by.binding('myForm.input.$valid'));
20843 var input = element(by.model('example.value'));
20844
20845 // currently protractor/webdriver does not support
20846 // sending keys to all known HTML5 input controls
20847 // for various browsers (https://github.com/angular/protractor/issues/562).
20848 function setInput(val) {
20849 // set the value of the element and force validation.
20850 var scr = "var ipt = document.getElementById('exampleInput'); " +
20851 "ipt.value = '" + val + "';" +
20852 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20853 browser.executeScript(scr);
20854 }
20855
20856 it('should initialize to model', function() {
20857 expect(value.getText()).toContain('2010-12-28T14:57:00');
20858 expect(valid.getText()).toContain('myForm.input.$valid = true');
20859 });
20860
20861 it('should be invalid if empty', function() {
20862 setInput('');
20863 expect(value.getText()).toEqual('value =');
20864 expect(valid.getText()).toContain('myForm.input.$valid = false');
20865 });
20866
20867 it('should be invalid if over max', function() {
20868 setInput('2015-01-01T23:59:00');
20869 expect(value.getText()).toContain('');
20870 expect(valid.getText()).toContain('myForm.input.$valid = false');
20871 });
20872 </file>
20873 </example>
20874 */
20875 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
20876 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
20877 'yyyy-MM-ddTHH:mm:ss.sss'),
20878
20879 /**
20880 * @ngdoc input
20881 * @name input[time]
20882 *
20883 * @description
20884 * Input with time validation and transformation. In browsers that do not yet support
20885 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20886 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
20887 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
20888 *
20889 * The model must always be a Date object, otherwise Angular will throw an error.
20890 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20891 *
20892 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20893 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20894 *
20895 * @param {string} ngModel Assignable angular expression to data-bind to.
20896 * @param {string=} name Property name of the form under which the control is published.
20897 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20898 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
20899 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
20900 * native HTML5 constraint validation.
20901 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20902 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
20903 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
20904 * native HTML5 constraint validation.
20905 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
20906 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20907 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
20908 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20909 * @param {string=} required Sets `required` validation error key if the value is not entered.
20910 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20911 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20912 * `required` when you want to data-bind to the `required` attribute.
20913 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20914 * interaction with the input element.
20915 *
20916 * @example
20917 <example name="time-input-directive" module="timeExample">
20918 <file name="index.html">
20919 <script>
20920 angular.module('timeExample', [])
20921 .controller('DateController', ['$scope', function($scope) {
20922 $scope.example = {
20923 value: new Date(1970, 0, 1, 14, 57, 0)
20924 };
20925 }]);
20926 </script>
20927 <form name="myForm" ng-controller="DateController as dateCtrl">
20928 <label for="exampleInput">Pick a between 8am and 5pm:</label>
20929 <input type="time" id="exampleInput" name="input" ng-model="example.value"
20930 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
20931 <div role="alert">
20932 <span class="error" ng-show="myForm.input.$error.required">
20933 Required!</span>
20934 <span class="error" ng-show="myForm.input.$error.time">
20935 Not a valid date!</span>
20936 </div>
20937 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
20938 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20939 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20940 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20941 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20942 </form>
20943 </file>
20944 <file name="protractor.js" type="protractor">
20945 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
20946 var valid = element(by.binding('myForm.input.$valid'));
20947 var input = element(by.model('example.value'));
20948
20949 // currently protractor/webdriver does not support
20950 // sending keys to all known HTML5 input controls
20951 // for various browsers (https://github.com/angular/protractor/issues/562).
20952 function setInput(val) {
20953 // set the value of the element and force validation.
20954 var scr = "var ipt = document.getElementById('exampleInput'); " +
20955 "ipt.value = '" + val + "';" +
20956 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20957 browser.executeScript(scr);
20958 }
20959
20960 it('should initialize to model', function() {
20961 expect(value.getText()).toContain('14:57:00');
20962 expect(valid.getText()).toContain('myForm.input.$valid = true');
20963 });
20964
20965 it('should be invalid if empty', function() {
20966 setInput('');
20967 expect(value.getText()).toEqual('value =');
20968 expect(valid.getText()).toContain('myForm.input.$valid = false');
20969 });
20970
20971 it('should be invalid if over max', function() {
20972 setInput('23:59:00');
20973 expect(value.getText()).toContain('');
20974 expect(valid.getText()).toContain('myForm.input.$valid = false');
20975 });
20976 </file>
20977 </example>
20978 */
20979 'time': createDateInputType('time', TIME_REGEXP,
20980 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
20981 'HH:mm:ss.sss'),
20982
20983 /**
20984 * @ngdoc input
20985 * @name input[week]
20986 *
20987 * @description
20988 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
20989 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20990 * week format (yyyy-W##), for example: `2013-W02`.
20991 *
20992 * The model must always be a Date object, otherwise Angular will throw an error.
20993 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20994 *
20995 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20996 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20997 *
20998 * @param {string} ngModel Assignable angular expression to data-bind to.
20999 * @param {string=} name Property name of the form under which the control is published.
21000 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21001 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21002 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21003 * native HTML5 constraint validation.
21004 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21005 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21006 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21007 * native HTML5 constraint validation.
21008 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21009 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21010 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21011 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21012 * @param {string=} required Sets `required` validation error key if the value is not entered.
21013 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21014 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21015 * `required` when you want to data-bind to the `required` attribute.
21016 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21017 * interaction with the input element.
21018 *
21019 * @example
21020 <example name="week-input-directive" module="weekExample">
21021 <file name="index.html">
21022 <script>
21023 angular.module('weekExample', [])
21024 .controller('DateController', ['$scope', function($scope) {
21025 $scope.example = {
21026 value: new Date(2013, 0, 3)
21027 };
21028 }]);
21029 </script>
21030 <form name="myForm" ng-controller="DateController as dateCtrl">
21031 <label>Pick a date between in 2013:
21032 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21033 placeholder="YYYY-W##" min="2012-W32"
21034 max="2013-W52" required />
21035 </label>
21036 <div role="alert">
21037 <span class="error" ng-show="myForm.input.$error.required">
21038 Required!</span>
21039 <span class="error" ng-show="myForm.input.$error.week">
21040 Not a valid date!</span>
21041 </div>
21042 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21043 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21044 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21045 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21046 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21047 </form>
21048 </file>
21049 <file name="protractor.js" type="protractor">
21050 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21051 var valid = element(by.binding('myForm.input.$valid'));
21052 var input = element(by.model('example.value'));
21053
21054 // currently protractor/webdriver does not support
21055 // sending keys to all known HTML5 input controls
21056 // for various browsers (https://github.com/angular/protractor/issues/562).
21057 function setInput(val) {
21058 // set the value of the element and force validation.
21059 var scr = "var ipt = document.getElementById('exampleInput'); " +
21060 "ipt.value = '" + val + "';" +
21061 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21062 browser.executeScript(scr);
21063 }
21064
21065 it('should initialize to model', function() {
21066 expect(value.getText()).toContain('2013-W01');
21067 expect(valid.getText()).toContain('myForm.input.$valid = true');
21068 });
21069
21070 it('should be invalid if empty', function() {
21071 setInput('');
21072 expect(value.getText()).toEqual('value =');
21073 expect(valid.getText()).toContain('myForm.input.$valid = false');
21074 });
21075
21076 it('should be invalid if over max', function() {
21077 setInput('2015-W01');
21078 expect(value.getText()).toContain('');
21079 expect(valid.getText()).toContain('myForm.input.$valid = false');
21080 });
21081 </file>
21082 </example>
21083 */
21084 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21085
21086 /**
21087 * @ngdoc input
21088 * @name input[month]
21089 *
21090 * @description
21091 * Input with month validation and transformation. In browsers that do not yet support
21092 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21093 * month format (yyyy-MM), for example: `2009-01`.
21094 *
21095 * The model must always be a Date object, otherwise Angular will throw an error.
21096 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21097 * If the model is not set to the first of the month, the next view to model update will set it
21098 * to the first of the month.
21099 *
21100 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21101 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21102 *
21103 * @param {string} ngModel Assignable angular expression to data-bind to.
21104 * @param {string=} name Property name of the form under which the control is published.
21105 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21106 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21107 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21108 * native HTML5 constraint validation.
21109 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21110 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21111 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21112 * native HTML5 constraint validation.
21113 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21114 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21115 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21116 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21117
21118 * @param {string=} required Sets `required` validation error key if the value is not entered.
21119 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21120 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21121 * `required` when you want to data-bind to the `required` attribute.
21122 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21123 * interaction with the input element.
21124 *
21125 * @example
21126 <example name="month-input-directive" module="monthExample">
21127 <file name="index.html">
21128 <script>
21129 angular.module('monthExample', [])
21130 .controller('DateController', ['$scope', function($scope) {
21131 $scope.example = {
21132 value: new Date(2013, 9, 1)
21133 };
21134 }]);
21135 </script>
21136 <form name="myForm" ng-controller="DateController as dateCtrl">
21137 <label for="exampleInput">Pick a month in 2013:</label>
21138 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21139 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21140 <div role="alert">
21141 <span class="error" ng-show="myForm.input.$error.required">
21142 Required!</span>
21143 <span class="error" ng-show="myForm.input.$error.month">
21144 Not a valid month!</span>
21145 </div>
21146 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21147 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21148 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21149 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21150 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21151 </form>
21152 </file>
21153 <file name="protractor.js" type="protractor">
21154 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21155 var valid = element(by.binding('myForm.input.$valid'));
21156 var input = element(by.model('example.value'));
21157
21158 // currently protractor/webdriver does not support
21159 // sending keys to all known HTML5 input controls
21160 // for various browsers (https://github.com/angular/protractor/issues/562).
21161 function setInput(val) {
21162 // set the value of the element and force validation.
21163 var scr = "var ipt = document.getElementById('exampleInput'); " +
21164 "ipt.value = '" + val + "';" +
21165 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21166 browser.executeScript(scr);
21167 }
21168
21169 it('should initialize to model', function() {
21170 expect(value.getText()).toContain('2013-10');
21171 expect(valid.getText()).toContain('myForm.input.$valid = true');
21172 });
21173
21174 it('should be invalid if empty', function() {
21175 setInput('');
21176 expect(value.getText()).toEqual('value =');
21177 expect(valid.getText()).toContain('myForm.input.$valid = false');
21178 });
21179
21180 it('should be invalid if over max', function() {
21181 setInput('2015-01');
21182 expect(value.getText()).toContain('');
21183 expect(valid.getText()).toContain('myForm.input.$valid = false');
21184 });
21185 </file>
21186 </example>
21187 */
21188 'month': createDateInputType('month', MONTH_REGEXP,
21189 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21190 'yyyy-MM'),
21191
21192 /**
21193 * @ngdoc input
21194 * @name input[number]
21195 *
21196 * @description
21197 * Text input with number validation and transformation. Sets the `number` validation
21198 * error if not a valid number.
21199 *
21200 * <div class="alert alert-warning">
21201 * The model must always be of type `number` otherwise Angular will throw an error.
21202 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21203 * error docs for more information and an example of how to convert your model if necessary.
21204 * </div>
21205 *
21206 * ## Issues with HTML5 constraint validation
21207 *
21208 * In browsers that follow the
21209 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21210 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21211 * If a non-number is entered in the input, the browser will report the value as an empty string,
21212 * which means the view / model values in `ngModel` and subsequently the scope value
21213 * will also be an empty string.
21214 *
21215 *
21216 * @param {string} ngModel Assignable angular expression to data-bind to.
21217 * @param {string=} name Property name of the form under which the control is published.
21218 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21219 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21220 * @param {string=} required Sets `required` validation error key if the value is not entered.
21221 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21222 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21223 * `required` when you want to data-bind to the `required` attribute.
21224 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21225 * minlength.
21226 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21227 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21228 * any length.
21229 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21230 * that contains the regular expression body that will be converted to a regular expression
21231 * as in the ngPattern directive.
21232 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21233 * a RegExp found by evaluating the Angular expression given in the attribute value.
21234 * If the expression evaluates to a RegExp object, then this is used directly.
21235 * If the expression evaluates to a string, then it will be converted to a RegExp
21236 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21237 * `new RegExp('^abc$')`.<br />
21238 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21239 * start at the index of the last search's match, thus not taking the whole input value into
21240 * account.
21241 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21242 * interaction with the input element.
21243 *
21244 * @example
21245 <example name="number-input-directive" module="numberExample">
21246 <file name="index.html">
21247 <script>
21248 angular.module('numberExample', [])
21249 .controller('ExampleController', ['$scope', function($scope) {
21250 $scope.example = {
21251 value: 12
21252 };
21253 }]);
21254 </script>
21255 <form name="myForm" ng-controller="ExampleController">
21256 <label>Number:
21257 <input type="number" name="input" ng-model="example.value"
21258 min="0" max="99" required>
21259 </label>
21260 <div role="alert">
21261 <span class="error" ng-show="myForm.input.$error.required">
21262 Required!</span>
21263 <span class="error" ng-show="myForm.input.$error.number">
21264 Not valid number!</span>
21265 </div>
21266 <tt>value = {{example.value}}</tt><br/>
21267 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21268 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21269 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21270 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21271 </form>
21272 </file>
21273 <file name="protractor.js" type="protractor">
21274 var value = element(by.binding('example.value'));
21275 var valid = element(by.binding('myForm.input.$valid'));
21276 var input = element(by.model('example.value'));
21277
21278 it('should initialize to model', function() {
21279 expect(value.getText()).toContain('12');
21280 expect(valid.getText()).toContain('true');
21281 });
21282
21283 it('should be invalid if empty', function() {
21284 input.clear();
21285 input.sendKeys('');
21286 expect(value.getText()).toEqual('value =');
21287 expect(valid.getText()).toContain('false');
21288 });
21289
21290 it('should be invalid if over max', function() {
21291 input.clear();
21292 input.sendKeys('123');
21293 expect(value.getText()).toEqual('value =');
21294 expect(valid.getText()).toContain('false');
21295 });
21296 </file>
21297 </example>
21298 */
21299 'number': numberInputType,
21300
21301
21302 /**
21303 * @ngdoc input
21304 * @name input[url]
21305 *
21306 * @description
21307 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21308 * valid URL.
21309 *
21310 * <div class="alert alert-warning">
21311 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21312 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21313 * the built-in validators (see the {@link guide/forms Forms guide})
21314 * </div>
21315 *
21316 * @param {string} ngModel Assignable angular expression to data-bind to.
21317 * @param {string=} name Property name of the form under which the control is published.
21318 * @param {string=} required Sets `required` validation error key if the value is not entered.
21319 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21320 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21321 * `required` when you want to data-bind to the `required` attribute.
21322 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21323 * minlength.
21324 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21325 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21326 * any length.
21327 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21328 * that contains the regular expression body that will be converted to a regular expression
21329 * as in the ngPattern directive.
21330 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21331 * a RegExp found by evaluating the Angular expression given in the attribute value.
21332 * If the expression evaluates to a RegExp object, then this is used directly.
21333 * If the expression evaluates to a string, then it will be converted to a RegExp
21334 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21335 * `new RegExp('^abc$')`.<br />
21336 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21337 * start at the index of the last search's match, thus not taking the whole input value into
21338 * account.
21339 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21340 * interaction with the input element.
21341 *
21342 * @example
21343 <example name="url-input-directive" module="urlExample">
21344 <file name="index.html">
21345 <script>
21346 angular.module('urlExample', [])
21347 .controller('ExampleController', ['$scope', function($scope) {
21348 $scope.url = {
21349 text: 'http://google.com'
21350 };
21351 }]);
21352 </script>
21353 <form name="myForm" ng-controller="ExampleController">
21354 <label>URL:
21355 <input type="url" name="input" ng-model="url.text" required>
21356 <label>
21357 <div role="alert">
21358 <span class="error" ng-show="myForm.input.$error.required">
21359 Required!</span>
21360 <span class="error" ng-show="myForm.input.$error.url">
21361 Not valid url!</span>
21362 </div>
21363 <tt>text = {{url.text}}</tt><br/>
21364 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21365 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21366 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21367 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21368 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21369 </form>
21370 </file>
21371 <file name="protractor.js" type="protractor">
21372 var text = element(by.binding('url.text'));
21373 var valid = element(by.binding('myForm.input.$valid'));
21374 var input = element(by.model('url.text'));
21375
21376 it('should initialize to model', function() {
21377 expect(text.getText()).toContain('http://google.com');
21378 expect(valid.getText()).toContain('true');
21379 });
21380
21381 it('should be invalid if empty', function() {
21382 input.clear();
21383 input.sendKeys('');
21384
21385 expect(text.getText()).toEqual('text =');
21386 expect(valid.getText()).toContain('false');
21387 });
21388
21389 it('should be invalid if not url', function() {
21390 input.clear();
21391 input.sendKeys('box');
21392
21393 expect(valid.getText()).toContain('false');
21394 });
21395 </file>
21396 </example>
21397 */
21398 'url': urlInputType,
21399
21400
21401 /**
21402 * @ngdoc input
21403 * @name input[email]
21404 *
21405 * @description
21406 * Text input with email validation. Sets the `email` validation error key if not a valid email
21407 * address.
21408 *
21409 * <div class="alert alert-warning">
21410 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21411 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21412 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21413 * </div>
21414 *
21415 * @param {string} ngModel Assignable angular expression to data-bind to.
21416 * @param {string=} name Property name of the form under which the control is published.
21417 * @param {string=} required Sets `required` validation error key if the value is not entered.
21418 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21419 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21420 * `required` when you want to data-bind to the `required` attribute.
21421 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21422 * minlength.
21423 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21424 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21425 * any length.
21426 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21427 * that contains the regular expression body that will be converted to a regular expression
21428 * as in the ngPattern directive.
21429 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21430 * a RegExp found by evaluating the Angular expression given in the attribute value.
21431 * If the expression evaluates to a RegExp object, then this is used directly.
21432 * If the expression evaluates to a string, then it will be converted to a RegExp
21433 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21434 * `new RegExp('^abc$')`.<br />
21435 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21436 * start at the index of the last search's match, thus not taking the whole input value into
21437 * account.
21438 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21439 * interaction with the input element.
21440 *
21441 * @example
21442 <example name="email-input-directive" module="emailExample">
21443 <file name="index.html">
21444 <script>
21445 angular.module('emailExample', [])
21446 .controller('ExampleController', ['$scope', function($scope) {
21447 $scope.email = {
21448 text: 'me@example.com'
21449 };
21450 }]);
21451 </script>
21452 <form name="myForm" ng-controller="ExampleController">
21453 <label>Email:
21454 <input type="email" name="input" ng-model="email.text" required>
21455 </label>
21456 <div role="alert">
21457 <span class="error" ng-show="myForm.input.$error.required">
21458 Required!</span>
21459 <span class="error" ng-show="myForm.input.$error.email">
21460 Not valid email!</span>
21461 </div>
21462 <tt>text = {{email.text}}</tt><br/>
21463 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21464 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21465 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21466 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21467 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21468 </form>
21469 </file>
21470 <file name="protractor.js" type="protractor">
21471 var text = element(by.binding('email.text'));
21472 var valid = element(by.binding('myForm.input.$valid'));
21473 var input = element(by.model('email.text'));
21474
21475 it('should initialize to model', function() {
21476 expect(text.getText()).toContain('me@example.com');
21477 expect(valid.getText()).toContain('true');
21478 });
21479
21480 it('should be invalid if empty', function() {
21481 input.clear();
21482 input.sendKeys('');
21483 expect(text.getText()).toEqual('text =');
21484 expect(valid.getText()).toContain('false');
21485 });
21486
21487 it('should be invalid if not email', function() {
21488 input.clear();
21489 input.sendKeys('xxx');
21490
21491 expect(valid.getText()).toContain('false');
21492 });
21493 </file>
21494 </example>
21495 */
21496 'email': emailInputType,
21497
21498
21499 /**
21500 * @ngdoc input
21501 * @name input[radio]
21502 *
21503 * @description
21504 * HTML radio button.
21505 *
21506 * @param {string} ngModel Assignable angular expression to data-bind to.
21507 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21508 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21509 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21510 * @param {string=} name Property name of the form under which the control is published.
21511 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21512 * interaction with the input element.
21513 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21514 * is selected. Should be used instead of the `value` attribute if you need
21515 * a non-string `ngModel` (`boolean`, `array`, ...).
21516 *
21517 * @example
21518 <example name="radio-input-directive" module="radioExample">
21519 <file name="index.html">
21520 <script>
21521 angular.module('radioExample', [])
21522 .controller('ExampleController', ['$scope', function($scope) {
21523 $scope.color = {
21524 name: 'blue'
21525 };
21526 $scope.specialValue = {
21527 "id": "12345",
21528 "value": "green"
21529 };
21530 }]);
21531 </script>
21532 <form name="myForm" ng-controller="ExampleController">
21533 <label>
21534 <input type="radio" ng-model="color.name" value="red">
21535 Red
21536 </label><br/>
21537 <label>
21538 <input type="radio" ng-model="color.name" ng-value="specialValue">
21539 Green
21540 </label><br/>
21541 <label>
21542 <input type="radio" ng-model="color.name" value="blue">
21543 Blue
21544 </label><br/>
21545 <tt>color = {{color.name | json}}</tt><br/>
21546 </form>
21547 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21548 </file>
21549 <file name="protractor.js" type="protractor">
21550 it('should change state', function() {
21551 var color = element(by.binding('color.name'));
21552
21553 expect(color.getText()).toContain('blue');
21554
21555 element.all(by.model('color.name')).get(0).click();
21556
21557 expect(color.getText()).toContain('red');
21558 });
21559 </file>
21560 </example>
21561 */
21562 'radio': radioInputType,
21563
21564
21565 /**
21566 * @ngdoc input
21567 * @name input[checkbox]
21568 *
21569 * @description
21570 * HTML checkbox.
21571 *
21572 * @param {string} ngModel Assignable angular expression to data-bind to.
21573 * @param {string=} name Property name of the form under which the control is published.
21574 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21575 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21576 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21577 * interaction with the input element.
21578 *
21579 * @example
21580 <example name="checkbox-input-directive" module="checkboxExample">
21581 <file name="index.html">
21582 <script>
21583 angular.module('checkboxExample', [])
21584 .controller('ExampleController', ['$scope', function($scope) {
21585 $scope.checkboxModel = {
21586 value1 : true,
21587 value2 : 'YES'
21588 };
21589 }]);
21590 </script>
21591 <form name="myForm" ng-controller="ExampleController">
21592 <label>Value1:
21593 <input type="checkbox" ng-model="checkboxModel.value1">
21594 </label><br/>
21595 <label>Value2:
21596 <input type="checkbox" ng-model="checkboxModel.value2"
21597 ng-true-value="'YES'" ng-false-value="'NO'">
21598 </label><br/>
21599 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21600 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21601 </form>
21602 </file>
21603 <file name="protractor.js" type="protractor">
21604 it('should change state', function() {
21605 var value1 = element(by.binding('checkboxModel.value1'));
21606 var value2 = element(by.binding('checkboxModel.value2'));
21607
21608 expect(value1.getText()).toContain('true');
21609 expect(value2.getText()).toContain('YES');
21610
21611 element(by.model('checkboxModel.value1')).click();
21612 element(by.model('checkboxModel.value2')).click();
21613
21614 expect(value1.getText()).toContain('false');
21615 expect(value2.getText()).toContain('NO');
21616 });
21617 </file>
21618 </example>
21619 */
21620 'checkbox': checkboxInputType,
21621
21622 'hidden': noop,
21623 'button': noop,
21624 'submit': noop,
21625 'reset': noop,
21626 'file': noop
21627};
21628
21629function stringBasedInputType(ctrl) {
21630 ctrl.$formatters.push(function(value) {
21631 return ctrl.$isEmpty(value) ? value : value.toString();
21632 });
21633}
21634
21635function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21636 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21637 stringBasedInputType(ctrl);
21638}
21639
21640function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21641 var type = lowercase(element[0].type);
21642
21643 // In composition mode, users are still inputing intermediate text buffer,
21644 // hold the listener until composition is done.
21645 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21646 if (!$sniffer.android) {
21647 var composing = false;
21648
21649 element.on('compositionstart', function(data) {
21650 composing = true;
21651 });
21652
21653 element.on('compositionend', function() {
21654 composing = false;
21655 listener();
21656 });
21657 }
21658
21659 var listener = function(ev) {
21660 if (timeout) {
21661 $browser.defer.cancel(timeout);
21662 timeout = null;
21663 }
21664 if (composing) return;
21665 var value = element.val(),
21666 event = ev && ev.type;
21667
21668 // By default we will trim the value
21669 // If the attribute ng-trim exists we will avoid trimming
21670 // If input type is 'password', the value is never trimmed
21671 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21672 value = trim(value);
21673 }
21674
21675 // If a control is suffering from bad input (due to native validators), browsers discard its
21676 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21677 // control's value is the same empty value twice in a row.
21678 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21679 ctrl.$setViewValue(value, event);
21680 }
21681 };
21682
21683 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21684 // input event on backspace, delete or cut
21685 if ($sniffer.hasEvent('input')) {
21686 element.on('input', listener);
21687 } else {
21688 var timeout;
21689
21690 var deferListener = function(ev, input, origValue) {
21691 if (!timeout) {
21692 timeout = $browser.defer(function() {
21693 timeout = null;
21694 if (!input || input.value !== origValue) {
21695 listener(ev);
21696 }
21697 });
21698 }
21699 };
21700
21701 element.on('keydown', function(event) {
21702 var key = event.keyCode;
21703
21704 // ignore
21705 // command modifiers arrows
21706 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21707
21708 deferListener(event, this, this.value);
21709 });
21710
21711 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21712 if ($sniffer.hasEvent('paste')) {
21713 element.on('paste cut', deferListener);
21714 }
21715 }
21716
21717 // if user paste into input using mouse on older browser
21718 // or form autocomplete on newer browser, we need "change" event to catch it
21719 element.on('change', listener);
21720
21721 ctrl.$render = function() {
21722 // Workaround for Firefox validation #12102.
21723 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21724 if (element.val() !== value) {
21725 element.val(value);
21726 }
21727 };
21728}
21729
21730function weekParser(isoWeek, existingDate) {
21731 if (isDate(isoWeek)) {
21732 return isoWeek;
21733 }
21734
21735 if (isString(isoWeek)) {
21736 WEEK_REGEXP.lastIndex = 0;
21737 var parts = WEEK_REGEXP.exec(isoWeek);
21738 if (parts) {
21739 var year = +parts[1],
21740 week = +parts[2],
21741 hours = 0,
21742 minutes = 0,
21743 seconds = 0,
21744 milliseconds = 0,
21745 firstThurs = getFirstThursdayOfYear(year),
21746 addDays = (week - 1) * 7;
21747
21748 if (existingDate) {
21749 hours = existingDate.getHours();
21750 minutes = existingDate.getMinutes();
21751 seconds = existingDate.getSeconds();
21752 milliseconds = existingDate.getMilliseconds();
21753 }
21754
21755 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21756 }
21757 }
21758
21759 return NaN;
21760}
21761
21762function createDateParser(regexp, mapping) {
21763 return function(iso, date) {
21764 var parts, map;
21765
21766 if (isDate(iso)) {
21767 return iso;
21768 }
21769
21770 if (isString(iso)) {
21771 // When a date is JSON'ified to wraps itself inside of an extra
21772 // set of double quotes. This makes the date parsing code unable
21773 // to match the date string and parse it as a date.
21774 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21775 iso = iso.substring(1, iso.length - 1);
21776 }
21777 if (ISO_DATE_REGEXP.test(iso)) {
21778 return new Date(iso);
21779 }
21780 regexp.lastIndex = 0;
21781 parts = regexp.exec(iso);
21782
21783 if (parts) {
21784 parts.shift();
21785 if (date) {
21786 map = {
21787 yyyy: date.getFullYear(),
21788 MM: date.getMonth() + 1,
21789 dd: date.getDate(),
21790 HH: date.getHours(),
21791 mm: date.getMinutes(),
21792 ss: date.getSeconds(),
21793 sss: date.getMilliseconds() / 1000
21794 };
21795 } else {
21796 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21797 }
21798
21799 forEach(parts, function(part, index) {
21800 if (index < mapping.length) {
21801 map[mapping[index]] = +part;
21802 }
21803 });
21804 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21805 }
21806 }
21807
21808 return NaN;
21809 };
21810}
21811
21812function createDateInputType(type, regexp, parseDate, format) {
21813 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21814 badInputChecker(scope, element, attr, ctrl);
21815 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21816 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21817 var previousDate;
21818
21819 ctrl.$$parserName = type;
21820 ctrl.$parsers.push(function(value) {
21821 if (ctrl.$isEmpty(value)) return null;
21822 if (regexp.test(value)) {
21823 // Note: We cannot read ctrl.$modelValue, as there might be a different
21824 // parser/formatter in the processing chain so that the model
21825 // contains some different data format!
21826 var parsedDate = parseDate(value, previousDate);
21827 if (timezone) {
21828 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21829 }
21830 return parsedDate;
21831 }
21832 return undefined;
21833 });
21834
21835 ctrl.$formatters.push(function(value) {
21836 if (value && !isDate(value)) {
21837 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21838 }
21839 if (isValidDate(value)) {
21840 previousDate = value;
21841 if (previousDate && timezone) {
21842 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21843 }
21844 return $filter('date')(value, format, timezone);
21845 } else {
21846 previousDate = null;
21847 return '';
21848 }
21849 });
21850
21851 if (isDefined(attr.min) || attr.ngMin) {
21852 var minVal;
21853 ctrl.$validators.min = function(value) {
21854 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21855 };
21856 attr.$observe('min', function(val) {
21857 minVal = parseObservedDateValue(val);
21858 ctrl.$validate();
21859 });
21860 }
21861
21862 if (isDefined(attr.max) || attr.ngMax) {
21863 var maxVal;
21864 ctrl.$validators.max = function(value) {
21865 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
21866 };
21867 attr.$observe('max', function(val) {
21868 maxVal = parseObservedDateValue(val);
21869 ctrl.$validate();
21870 });
21871 }
21872
21873 function isValidDate(value) {
21874 // Invalid Date: getTime() returns NaN
21875 return value && !(value.getTime && value.getTime() !== value.getTime());
21876 }
21877
21878 function parseObservedDateValue(val) {
21879 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
21880 }
21881 };
21882}
21883
21884function badInputChecker(scope, element, attr, ctrl) {
21885 var node = element[0];
21886 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
21887 if (nativeValidation) {
21888 ctrl.$parsers.push(function(value) {
21889 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
21890 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
21891 // - also sets validity.badInput (should only be validity.typeMismatch).
21892 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
21893 // - can ignore this case as we can still read out the erroneous email...
21894 return validity.badInput && !validity.typeMismatch ? undefined : value;
21895 });
21896 }
21897}
21898
21899function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21900 badInputChecker(scope, element, attr, ctrl);
21901 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21902
21903 ctrl.$$parserName = 'number';
21904 ctrl.$parsers.push(function(value) {
21905 if (ctrl.$isEmpty(value)) return null;
21906 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
21907 return undefined;
21908 });
21909
21910 ctrl.$formatters.push(function(value) {
21911 if (!ctrl.$isEmpty(value)) {
21912 if (!isNumber(value)) {
21913 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
21914 }
21915 value = value.toString();
21916 }
21917 return value;
21918 });
21919
21920 if (isDefined(attr.min) || attr.ngMin) {
21921 var minVal;
21922 ctrl.$validators.min = function(value) {
21923 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
21924 };
21925
21926 attr.$observe('min', function(val) {
21927 if (isDefined(val) && !isNumber(val)) {
21928 val = parseFloat(val, 10);
21929 }
21930 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
21931 // TODO(matsko): implement validateLater to reduce number of validations
21932 ctrl.$validate();
21933 });
21934 }
21935
21936 if (isDefined(attr.max) || attr.ngMax) {
21937 var maxVal;
21938 ctrl.$validators.max = function(value) {
21939 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
21940 };
21941
21942 attr.$observe('max', function(val) {
21943 if (isDefined(val) && !isNumber(val)) {
21944 val = parseFloat(val, 10);
21945 }
21946 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
21947 // TODO(matsko): implement validateLater to reduce number of validations
21948 ctrl.$validate();
21949 });
21950 }
21951}
21952
21953function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21954 // Note: no badInputChecker here by purpose as `url` is only a validation
21955 // in browsers, i.e. we can always read out input.value even if it is not valid!
21956 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21957 stringBasedInputType(ctrl);
21958
21959 ctrl.$$parserName = 'url';
21960 ctrl.$validators.url = function(modelValue, viewValue) {
21961 var value = modelValue || viewValue;
21962 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
21963 };
21964}
21965
21966function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21967 // Note: no badInputChecker here by purpose as `url` is only a validation
21968 // in browsers, i.e. we can always read out input.value even if it is not valid!
21969 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21970 stringBasedInputType(ctrl);
21971
21972 ctrl.$$parserName = 'email';
21973 ctrl.$validators.email = function(modelValue, viewValue) {
21974 var value = modelValue || viewValue;
21975 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
21976 };
21977}
21978
21979function radioInputType(scope, element, attr, ctrl) {
21980 // make the name unique, if not defined
21981 if (isUndefined(attr.name)) {
21982 element.attr('name', nextUid());
21983 }
21984
21985 var listener = function(ev) {
21986 if (element[0].checked) {
21987 ctrl.$setViewValue(attr.value, ev && ev.type);
21988 }
21989 };
21990
21991 element.on('click', listener);
21992
21993 ctrl.$render = function() {
21994 var value = attr.value;
21995 element[0].checked = (value == ctrl.$viewValue);
21996 };
21997
21998 attr.$observe('value', ctrl.$render);
21999}
22000
22001function parseConstantExpr($parse, context, name, expression, fallback) {
22002 var parseFn;
22003 if (isDefined(expression)) {
22004 parseFn = $parse(expression);
22005 if (!parseFn.constant) {
22006 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22007 '`{1}`.', name, expression);
22008 }
22009 return parseFn(context);
22010 }
22011 return fallback;
22012}
22013
22014function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22015 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22016 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22017
22018 var listener = function(ev) {
22019 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22020 };
22021
22022 element.on('click', listener);
22023
22024 ctrl.$render = function() {
22025 element[0].checked = ctrl.$viewValue;
22026 };
22027
22028 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22029 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22030 // it to a boolean.
22031 ctrl.$isEmpty = function(value) {
22032 return value === false;
22033 };
22034
22035 ctrl.$formatters.push(function(value) {
22036 return equals(value, trueValue);
22037 });
22038
22039 ctrl.$parsers.push(function(value) {
22040 return value ? trueValue : falseValue;
22041 });
22042}
22043
22044
22045/**
22046 * @ngdoc directive
22047 * @name textarea
22048 * @restrict E
22049 *
22050 * @description
22051 * HTML textarea element control with angular data-binding. The data-binding and validation
22052 * properties of this element are exactly the same as those of the
22053 * {@link ng.directive:input input element}.
22054 *
22055 * @param {string} ngModel Assignable angular expression to data-bind to.
22056 * @param {string=} name Property name of the form under which the control is published.
22057 * @param {string=} required Sets `required` validation error key if the value is not entered.
22058 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22059 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22060 * `required` when you want to data-bind to the `required` attribute.
22061 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22062 * minlength.
22063 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22064 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22065 * length.
22066 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22067 * a RegExp found by evaluating the Angular expression given in the attribute value.
22068 * If the expression evaluates to a RegExp object, then this is used directly.
22069 * If the expression evaluates to a string, then it will be converted to a RegExp
22070 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22071 * `new RegExp('^abc$')`.<br />
22072 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22073 * start at the index of the last search's match, thus not taking the whole input value into
22074 * account.
22075 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22076 * interaction with the input element.
22077 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22078 */
22079
22080
22081/**
22082 * @ngdoc directive
22083 * @name input
22084 * @restrict E
22085 *
22086 * @description
22087 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22088 * input state control, and validation.
22089 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22090 *
22091 * <div class="alert alert-warning">
22092 * **Note:** Not every feature offered is available for all input types.
22093 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22094 * </div>
22095 *
22096 * @param {string} ngModel Assignable angular expression to data-bind to.
22097 * @param {string=} name Property name of the form under which the control is published.
22098 * @param {string=} required Sets `required` validation error key if the value is not entered.
22099 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22100 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22101 * minlength.
22102 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22103 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22104 * length.
22105 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22106 * a RegExp found by evaluating the Angular expression given in the attribute value.
22107 * If the expression evaluates to a RegExp object, then this is used directly.
22108 * If the expression evaluates to a string, then it will be converted to a RegExp
22109 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22110 * `new RegExp('^abc$')`.<br />
22111 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22112 * start at the index of the last search's match, thus not taking the whole input value into
22113 * account.
22114 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22115 * interaction with the input element.
22116 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22117 * This parameter is ignored for input[type=password] controls, which will never trim the
22118 * input.
22119 *
22120 * @example
22121 <example name="input-directive" module="inputExample">
22122 <file name="index.html">
22123 <script>
22124 angular.module('inputExample', [])
22125 .controller('ExampleController', ['$scope', function($scope) {
22126 $scope.user = {name: 'guest', last: 'visitor'};
22127 }]);
22128 </script>
22129 <div ng-controller="ExampleController">
22130 <form name="myForm">
22131 <label>
22132 User name:
22133 <input type="text" name="userName" ng-model="user.name" required>
22134 </label>
22135 <div role="alert">
22136 <span class="error" ng-show="myForm.userName.$error.required">
22137 Required!</span>
22138 </div>
22139 <label>
22140 Last name:
22141 <input type="text" name="lastName" ng-model="user.last"
22142 ng-minlength="3" ng-maxlength="10">
22143 </label>
22144 <div role="alert">
22145 <span class="error" ng-show="myForm.lastName.$error.minlength">
22146 Too short!</span>
22147 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22148 Too long!</span>
22149 </div>
22150 </form>
22151 <hr>
22152 <tt>user = {{user}}</tt><br/>
22153 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22154 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22155 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22156 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22157 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22158 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22159 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22160 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22161 </div>
22162 </file>
22163 <file name="protractor.js" type="protractor">
22164 var user = element(by.exactBinding('user'));
22165 var userNameValid = element(by.binding('myForm.userName.$valid'));
22166 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22167 var lastNameError = element(by.binding('myForm.lastName.$error'));
22168 var formValid = element(by.binding('myForm.$valid'));
22169 var userNameInput = element(by.model('user.name'));
22170 var userLastInput = element(by.model('user.last'));
22171
22172 it('should initialize to model', function() {
22173 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22174 expect(userNameValid.getText()).toContain('true');
22175 expect(formValid.getText()).toContain('true');
22176 });
22177
22178 it('should be invalid if empty when required', function() {
22179 userNameInput.clear();
22180 userNameInput.sendKeys('');
22181
22182 expect(user.getText()).toContain('{"last":"visitor"}');
22183 expect(userNameValid.getText()).toContain('false');
22184 expect(formValid.getText()).toContain('false');
22185 });
22186
22187 it('should be valid if empty when min length is set', function() {
22188 userLastInput.clear();
22189 userLastInput.sendKeys('');
22190
22191 expect(user.getText()).toContain('{"name":"guest","last":""}');
22192 expect(lastNameValid.getText()).toContain('true');
22193 expect(formValid.getText()).toContain('true');
22194 });
22195
22196 it('should be invalid if less than required min length', function() {
22197 userLastInput.clear();
22198 userLastInput.sendKeys('xx');
22199
22200 expect(user.getText()).toContain('{"name":"guest"}');
22201 expect(lastNameValid.getText()).toContain('false');
22202 expect(lastNameError.getText()).toContain('minlength');
22203 expect(formValid.getText()).toContain('false');
22204 });
22205
22206 it('should be invalid if longer than max length', function() {
22207 userLastInput.clear();
22208 userLastInput.sendKeys('some ridiculously long name');
22209
22210 expect(user.getText()).toContain('{"name":"guest"}');
22211 expect(lastNameValid.getText()).toContain('false');
22212 expect(lastNameError.getText()).toContain('maxlength');
22213 expect(formValid.getText()).toContain('false');
22214 });
22215 </file>
22216 </example>
22217 */
22218var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22219 function($browser, $sniffer, $filter, $parse) {
22220 return {
22221 restrict: 'E',
22222 require: ['?ngModel'],
22223 link: {
22224 pre: function(scope, element, attr, ctrls) {
22225 if (ctrls[0]) {
22226 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22227 $browser, $filter, $parse);
22228 }
22229 }
22230 }
22231 };
22232}];
22233
22234
22235
22236var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22237/**
22238 * @ngdoc directive
22239 * @name ngValue
22240 *
22241 * @description
22242 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22243 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22244 * the bound value.
22245 *
22246 * `ngValue` is useful when dynamically generating lists of radio buttons using
22247 * {@link ngRepeat `ngRepeat`}, as shown below.
22248 *
22249 * Likewise, `ngValue` can be used to generate `<option>` elements for
22250 * the {@link select `select`} element. In that case however, only strings are supported
22251 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22252 * Support for `select` models with non-string values is available via `ngOptions`.
22253 *
22254 * @element input
22255 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22256 * of the `input` element
22257 *
22258 * @example
22259 <example name="ngValue-directive" module="valueExample">
22260 <file name="index.html">
22261 <script>
22262 angular.module('valueExample', [])
22263 .controller('ExampleController', ['$scope', function($scope) {
22264 $scope.names = ['pizza', 'unicorns', 'robots'];
22265 $scope.my = { favorite: 'unicorns' };
22266 }]);
22267 </script>
22268 <form ng-controller="ExampleController">
22269 <h2>Which is your favorite?</h2>
22270 <label ng-repeat="name in names" for="{{name}}">
22271 {{name}}
22272 <input type="radio"
22273 ng-model="my.favorite"
22274 ng-value="name"
22275 id="{{name}}"
22276 name="favorite">
22277 </label>
22278 <div>You chose {{my.favorite}}</div>
22279 </form>
22280 </file>
22281 <file name="protractor.js" type="protractor">
22282 var favorite = element(by.binding('my.favorite'));
22283
22284 it('should initialize to model', function() {
22285 expect(favorite.getText()).toContain('unicorns');
22286 });
22287 it('should bind the values to the inputs', function() {
22288 element.all(by.model('my.favorite')).get(0).click();
22289 expect(favorite.getText()).toContain('pizza');
22290 });
22291 </file>
22292 </example>
22293 */
22294var ngValueDirective = function() {
22295 return {
22296 restrict: 'A',
22297 priority: 100,
22298 compile: function(tpl, tplAttr) {
22299 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22300 return function ngValueConstantLink(scope, elm, attr) {
22301 attr.$set('value', scope.$eval(attr.ngValue));
22302 };
22303 } else {
22304 return function ngValueLink(scope, elm, attr) {
22305 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22306 attr.$set('value', value);
22307 });
22308 };
22309 }
22310 }
22311 };
22312};
22313
22314/**
22315 * @ngdoc directive
22316 * @name ngBind
22317 * @restrict AC
22318 *
22319 * @description
22320 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22321 * with the value of a given expression, and to update the text content when the value of that
22322 * expression changes.
22323 *
22324 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22325 * `{{ expression }}` which is similar but less verbose.
22326 *
22327 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22328 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22329 * element attribute, it makes the bindings invisible to the user while the page is loading.
22330 *
22331 * An alternative solution to this problem would be using the
22332 * {@link ng.directive:ngCloak ngCloak} directive.
22333 *
22334 *
22335 * @element ANY
22336 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22337 *
22338 * @example
22339 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22340 <example module="bindExample">
22341 <file name="index.html">
22342 <script>
22343 angular.module('bindExample', [])
22344 .controller('ExampleController', ['$scope', function($scope) {
22345 $scope.name = 'Whirled';
22346 }]);
22347 </script>
22348 <div ng-controller="ExampleController">
22349 <label>Enter name: <input type="text" ng-model="name"></label><br>
22350 Hello <span ng-bind="name"></span>!
22351 </div>
22352 </file>
22353 <file name="protractor.js" type="protractor">
22354 it('should check ng-bind', function() {
22355 var nameInput = element(by.model('name'));
22356
22357 expect(element(by.binding('name')).getText()).toBe('Whirled');
22358 nameInput.clear();
22359 nameInput.sendKeys('world');
22360 expect(element(by.binding('name')).getText()).toBe('world');
22361 });
22362 </file>
22363 </example>
22364 */
22365var ngBindDirective = ['$compile', function($compile) {
22366 return {
22367 restrict: 'AC',
22368 compile: function ngBindCompile(templateElement) {
22369 $compile.$$addBindingClass(templateElement);
22370 return function ngBindLink(scope, element, attr) {
22371 $compile.$$addBindingInfo(element, attr.ngBind);
22372 element = element[0];
22373 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22374 element.textContent = isUndefined(value) ? '' : value;
22375 });
22376 };
22377 }
22378 };
22379}];
22380
22381
22382/**
22383 * @ngdoc directive
22384 * @name ngBindTemplate
22385 *
22386 * @description
22387 * The `ngBindTemplate` directive specifies that the element
22388 * text content should be replaced with the interpolation of the template
22389 * in the `ngBindTemplate` attribute.
22390 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22391 * expressions. This directive is needed since some HTML elements
22392 * (such as TITLE and OPTION) cannot contain SPAN elements.
22393 *
22394 * @element ANY
22395 * @param {string} ngBindTemplate template of form
22396 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22397 *
22398 * @example
22399 * Try it here: enter text in text box and watch the greeting change.
22400 <example module="bindExample">
22401 <file name="index.html">
22402 <script>
22403 angular.module('bindExample', [])
22404 .controller('ExampleController', ['$scope', function($scope) {
22405 $scope.salutation = 'Hello';
22406 $scope.name = 'World';
22407 }]);
22408 </script>
22409 <div ng-controller="ExampleController">
22410 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22411 <label>Name: <input type="text" ng-model="name"></label><br>
22412 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22413 </div>
22414 </file>
22415 <file name="protractor.js" type="protractor">
22416 it('should check ng-bind', function() {
22417 var salutationElem = element(by.binding('salutation'));
22418 var salutationInput = element(by.model('salutation'));
22419 var nameInput = element(by.model('name'));
22420
22421 expect(salutationElem.getText()).toBe('Hello World!');
22422
22423 salutationInput.clear();
22424 salutationInput.sendKeys('Greetings');
22425 nameInput.clear();
22426 nameInput.sendKeys('user');
22427
22428 expect(salutationElem.getText()).toBe('Greetings user!');
22429 });
22430 </file>
22431 </example>
22432 */
22433var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22434 return {
22435 compile: function ngBindTemplateCompile(templateElement) {
22436 $compile.$$addBindingClass(templateElement);
22437 return function ngBindTemplateLink(scope, element, attr) {
22438 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22439 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22440 element = element[0];
22441 attr.$observe('ngBindTemplate', function(value) {
22442 element.textContent = isUndefined(value) ? '' : value;
22443 });
22444 };
22445 }
22446 };
22447}];
22448
22449
22450/**
22451 * @ngdoc directive
22452 * @name ngBindHtml
22453 *
22454 * @description
22455 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22456 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22457 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22458 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22459 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22460 *
22461 * You may also bypass sanitization for values you know are safe. To do so, bind to
22462 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22463 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22464 *
22465 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22466 * will have an exception (instead of an exploit.)
22467 *
22468 * @element ANY
22469 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22470 *
22471 * @example
22472
22473 <example module="bindHtmlExample" deps="angular-sanitize.js">
22474 <file name="index.html">
22475 <div ng-controller="ExampleController">
22476 <p ng-bind-html="myHTML"></p>
22477 </div>
22478 </file>
22479
22480 <file name="script.js">
22481 angular.module('bindHtmlExample', ['ngSanitize'])
22482 .controller('ExampleController', ['$scope', function($scope) {
22483 $scope.myHTML =
22484 'I am an <code>HTML</code>string with ' +
22485 '<a href="#">links!</a> and other <em>stuff</em>';
22486 }]);
22487 </file>
22488
22489 <file name="protractor.js" type="protractor">
22490 it('should check ng-bind-html', function() {
22491 expect(element(by.binding('myHTML')).getText()).toBe(
22492 'I am an HTMLstring with links! and other stuff');
22493 });
22494 </file>
22495 </example>
22496 */
22497var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22498 return {
22499 restrict: 'A',
22500 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22501 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22502 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22503 return (value || '').toString();
22504 });
22505 $compile.$$addBindingClass(tElement);
22506
22507 return function ngBindHtmlLink(scope, element, attr) {
22508 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22509
22510 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22511 // we re-evaluate the expr because we want a TrustedValueHolderType
22512 // for $sce, not a string
22513 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22514 });
22515 };
22516 }
22517 };
22518}];
22519
22520/**
22521 * @ngdoc directive
22522 * @name ngChange
22523 *
22524 * @description
22525 * Evaluate the given expression when the user changes the input.
22526 * The expression is evaluated immediately, unlike the JavaScript onchange event
22527 * which only triggers at the end of a change (usually, when the user leaves the
22528 * form element or presses the return key).
22529 *
22530 * The `ngChange` expression is only evaluated when a change in the input value causes
22531 * a new value to be committed to the model.
22532 *
22533 * It will not be evaluated:
22534 * * if the value returned from the `$parsers` transformation pipeline has not changed
22535 * * if the input has continued to be invalid since the model will stay `null`
22536 * * if the model is changed programmatically and not by a change to the input value
22537 *
22538 *
22539 * Note, this directive requires `ngModel` to be present.
22540 *
22541 * @element input
22542 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22543 * in input value.
22544 *
22545 * @example
22546 * <example name="ngChange-directive" module="changeExample">
22547 * <file name="index.html">
22548 * <script>
22549 * angular.module('changeExample', [])
22550 * .controller('ExampleController', ['$scope', function($scope) {
22551 * $scope.counter = 0;
22552 * $scope.change = function() {
22553 * $scope.counter++;
22554 * };
22555 * }]);
22556 * </script>
22557 * <div ng-controller="ExampleController">
22558 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22559 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22560 * <label for="ng-change-example2">Confirmed</label><br />
22561 * <tt>debug = {{confirmed}}</tt><br/>
22562 * <tt>counter = {{counter}}</tt><br/>
22563 * </div>
22564 * </file>
22565 * <file name="protractor.js" type="protractor">
22566 * var counter = element(by.binding('counter'));
22567 * var debug = element(by.binding('confirmed'));
22568 *
22569 * it('should evaluate the expression if changing from view', function() {
22570 * expect(counter.getText()).toContain('0');
22571 *
22572 * element(by.id('ng-change-example1')).click();
22573 *
22574 * expect(counter.getText()).toContain('1');
22575 * expect(debug.getText()).toContain('true');
22576 * });
22577 *
22578 * it('should not evaluate the expression if changing from model', function() {
22579 * element(by.id('ng-change-example2')).click();
22580
22581 * expect(counter.getText()).toContain('0');
22582 * expect(debug.getText()).toContain('true');
22583 * });
22584 * </file>
22585 * </example>
22586 */
22587var ngChangeDirective = valueFn({
22588 restrict: 'A',
22589 require: 'ngModel',
22590 link: function(scope, element, attr, ctrl) {
22591 ctrl.$viewChangeListeners.push(function() {
22592 scope.$eval(attr.ngChange);
22593 });
22594 }
22595});
22596
22597function classDirective(name, selector) {
22598 name = 'ngClass' + name;
22599 return ['$animate', function($animate) {
22600 return {
22601 restrict: 'AC',
22602 link: function(scope, element, attr) {
22603 var oldVal;
22604
22605 scope.$watch(attr[name], ngClassWatchAction, true);
22606
22607 attr.$observe('class', function(value) {
22608 ngClassWatchAction(scope.$eval(attr[name]));
22609 });
22610
22611
22612 if (name !== 'ngClass') {
22613 scope.$watch('$index', function($index, old$index) {
22614 // jshint bitwise: false
22615 var mod = $index & 1;
22616 if (mod !== (old$index & 1)) {
22617 var classes = arrayClasses(scope.$eval(attr[name]));
22618 mod === selector ?
22619 addClasses(classes) :
22620 removeClasses(classes);
22621 }
22622 });
22623 }
22624
22625 function addClasses(classes) {
22626 var newClasses = digestClassCounts(classes, 1);
22627 attr.$addClass(newClasses);
22628 }
22629
22630 function removeClasses(classes) {
22631 var newClasses = digestClassCounts(classes, -1);
22632 attr.$removeClass(newClasses);
22633 }
22634
22635 function digestClassCounts(classes, count) {
22636 // Use createMap() to prevent class assumptions involving property
22637 // names in Object.prototype
22638 var classCounts = element.data('$classCounts') || createMap();
22639 var classesToUpdate = [];
22640 forEach(classes, function(className) {
22641 if (count > 0 || classCounts[className]) {
22642 classCounts[className] = (classCounts[className] || 0) + count;
22643 if (classCounts[className] === +(count > 0)) {
22644 classesToUpdate.push(className);
22645 }
22646 }
22647 });
22648 element.data('$classCounts', classCounts);
22649 return classesToUpdate.join(' ');
22650 }
22651
22652 function updateClasses(oldClasses, newClasses) {
22653 var toAdd = arrayDifference(newClasses, oldClasses);
22654 var toRemove = arrayDifference(oldClasses, newClasses);
22655 toAdd = digestClassCounts(toAdd, 1);
22656 toRemove = digestClassCounts(toRemove, -1);
22657 if (toAdd && toAdd.length) {
22658 $animate.addClass(element, toAdd);
22659 }
22660 if (toRemove && toRemove.length) {
22661 $animate.removeClass(element, toRemove);
22662 }
22663 }
22664
22665 function ngClassWatchAction(newVal) {
22666 if (selector === true || scope.$index % 2 === selector) {
22667 var newClasses = arrayClasses(newVal || []);
22668 if (!oldVal) {
22669 addClasses(newClasses);
22670 } else if (!equals(newVal,oldVal)) {
22671 var oldClasses = arrayClasses(oldVal);
22672 updateClasses(oldClasses, newClasses);
22673 }
22674 }
22675 oldVal = shallowCopy(newVal);
22676 }
22677 }
22678 };
22679
22680 function arrayDifference(tokens1, tokens2) {
22681 var values = [];
22682
22683 outer:
22684 for (var i = 0; i < tokens1.length; i++) {
22685 var token = tokens1[i];
22686 for (var j = 0; j < tokens2.length; j++) {
22687 if (token == tokens2[j]) continue outer;
22688 }
22689 values.push(token);
22690 }
22691 return values;
22692 }
22693
22694 function arrayClasses(classVal) {
22695 var classes = [];
22696 if (isArray(classVal)) {
22697 forEach(classVal, function(v) {
22698 classes = classes.concat(arrayClasses(v));
22699 });
22700 return classes;
22701 } else if (isString(classVal)) {
22702 return classVal.split(' ');
22703 } else if (isObject(classVal)) {
22704 forEach(classVal, function(v, k) {
22705 if (v) {
22706 classes = classes.concat(k.split(' '));
22707 }
22708 });
22709 return classes;
22710 }
22711 return classVal;
22712 }
22713 }];
22714}
22715
22716/**
22717 * @ngdoc directive
22718 * @name ngClass
22719 * @restrict AC
22720 *
22721 * @description
22722 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22723 * an expression that represents all classes to be added.
22724 *
22725 * The directive operates in three different ways, depending on which of three types the expression
22726 * evaluates to:
22727 *
22728 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22729 * names.
22730 *
22731 * 2. If the expression evaluates to an object, then for each key-value pair of the
22732 * object with a truthy value the corresponding key is used as a class name.
22733 *
22734 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22735 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22736 * to give you more control over what CSS classes appear. See the code below for an example of this.
22737 *
22738 *
22739 * The directive won't add duplicate classes if a particular class was already set.
22740 *
22741 * When the expression changes, the previously added classes are removed and only then are the
22742 * new classes added.
22743 *
22744 * @animations
22745 * **add** - happens just before the class is applied to the elements
22746 *
22747 * **remove** - happens just before the class is removed from the element
22748 *
22749 * @element ANY
22750 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22751 * of the evaluation can be a string representing space delimited class
22752 * names, an array, or a map of class names to boolean values. In the case of a map, the
22753 * names of the properties whose values are truthy will be added as css classes to the
22754 * element.
22755 *
22756 * @example Example that demonstrates basic bindings via ngClass directive.
22757 <example>
22758 <file name="index.html">
22759 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22760 <label>
22761 <input type="checkbox" ng-model="deleted">
22762 deleted (apply "strike" class)
22763 </label><br>
22764 <label>
22765 <input type="checkbox" ng-model="important">
22766 important (apply "bold" class)
22767 </label><br>
22768 <label>
22769 <input type="checkbox" ng-model="error">
22770 error (apply "has-error" class)
22771 </label>
22772 <hr>
22773 <p ng-class="style">Using String Syntax</p>
22774 <input type="text" ng-model="style"
22775 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22776 <hr>
22777 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22778 <input ng-model="style1"
22779 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22780 <input ng-model="style2"
22781 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22782 <input ng-model="style3"
22783 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22784 <hr>
22785 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22786 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22787 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22788 </file>
22789 <file name="style.css">
22790 .strike {
22791 text-decoration: line-through;
22792 }
22793 .bold {
22794 font-weight: bold;
22795 }
22796 .red {
22797 color: red;
22798 }
22799 .has-error {
22800 color: red;
22801 background-color: yellow;
22802 }
22803 .orange {
22804 color: orange;
22805 }
22806 </file>
22807 <file name="protractor.js" type="protractor">
22808 var ps = element.all(by.css('p'));
22809
22810 it('should let you toggle the class', function() {
22811
22812 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22813 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22814
22815 element(by.model('important')).click();
22816 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22817
22818 element(by.model('error')).click();
22819 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22820 });
22821
22822 it('should let you toggle string example', function() {
22823 expect(ps.get(1).getAttribute('class')).toBe('');
22824 element(by.model('style')).clear();
22825 element(by.model('style')).sendKeys('red');
22826 expect(ps.get(1).getAttribute('class')).toBe('red');
22827 });
22828
22829 it('array example should have 3 classes', function() {
22830 expect(ps.get(2).getAttribute('class')).toBe('');
22831 element(by.model('style1')).sendKeys('bold');
22832 element(by.model('style2')).sendKeys('strike');
22833 element(by.model('style3')).sendKeys('red');
22834 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22835 });
22836
22837 it('array with map example should have 2 classes', function() {
22838 expect(ps.last().getAttribute('class')).toBe('');
22839 element(by.model('style4')).sendKeys('bold');
22840 element(by.model('warning')).click();
22841 expect(ps.last().getAttribute('class')).toBe('bold orange');
22842 });
22843 </file>
22844 </example>
22845
22846 ## Animations
22847
22848 The example below demonstrates how to perform animations using ngClass.
22849
22850 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22851 <file name="index.html">
22852 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22853 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22854 <br>
22855 <span class="base-class" ng-class="myVar">Sample Text</span>
22856 </file>
22857 <file name="style.css">
22858 .base-class {
22859 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
22860 }
22861
22862 .base-class.my-class {
22863 color: red;
22864 font-size:3em;
22865 }
22866 </file>
22867 <file name="protractor.js" type="protractor">
22868 it('should check ng-class', function() {
22869 expect(element(by.css('.base-class')).getAttribute('class')).not.
22870 toMatch(/my-class/);
22871
22872 element(by.id('setbtn')).click();
22873
22874 expect(element(by.css('.base-class')).getAttribute('class')).
22875 toMatch(/my-class/);
22876
22877 element(by.id('clearbtn')).click();
22878
22879 expect(element(by.css('.base-class')).getAttribute('class')).not.
22880 toMatch(/my-class/);
22881 });
22882 </file>
22883 </example>
22884
22885
22886 ## ngClass and pre-existing CSS3 Transitions/Animations
22887 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
22888 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
22889 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
22890 to view the step by step details of {@link $animate#addClass $animate.addClass} and
22891 {@link $animate#removeClass $animate.removeClass}.
22892 */
22893var ngClassDirective = classDirective('', true);
22894
22895/**
22896 * @ngdoc directive
22897 * @name ngClassOdd
22898 * @restrict AC
22899 *
22900 * @description
22901 * The `ngClassOdd` and `ngClassEven` directives work exactly as
22902 * {@link ng.directive:ngClass ngClass}, except they work in
22903 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
22904 *
22905 * This directive can be applied only within the scope of an
22906 * {@link ng.directive:ngRepeat ngRepeat}.
22907 *
22908 * @element ANY
22909 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
22910 * of the evaluation can be a string representing space delimited class names or an array.
22911 *
22912 * @example
22913 <example>
22914 <file name="index.html">
22915 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
22916 <li ng-repeat="name in names">
22917 <span ng-class-odd="'odd'" ng-class-even="'even'">
22918 {{name}}
22919 </span>
22920 </li>
22921 </ol>
22922 </file>
22923 <file name="style.css">
22924 .odd {
22925 color: red;
22926 }
22927 .even {
22928 color: blue;
22929 }
22930 </file>
22931 <file name="protractor.js" type="protractor">
22932 it('should check ng-class-odd and ng-class-even', function() {
22933 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
22934 toMatch(/odd/);
22935 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
22936 toMatch(/even/);
22937 });
22938 </file>
22939 </example>
22940 */
22941var ngClassOddDirective = classDirective('Odd', 0);
22942
22943/**
22944 * @ngdoc directive
22945 * @name ngClassEven
22946 * @restrict AC
22947 *
22948 * @description
22949 * The `ngClassOdd` and `ngClassEven` directives work exactly as
22950 * {@link ng.directive:ngClass ngClass}, except they work in
22951 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
22952 *
22953 * This directive can be applied only within the scope of an
22954 * {@link ng.directive:ngRepeat ngRepeat}.
22955 *
22956 * @element ANY
22957 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
22958 * result of the evaluation can be a string representing space delimited class names or an array.
22959 *
22960 * @example
22961 <example>
22962 <file name="index.html">
22963 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
22964 <li ng-repeat="name in names">
22965 <span ng-class-odd="'odd'" ng-class-even="'even'">
22966 {{name}} &nbsp; &nbsp; &nbsp;
22967 </span>
22968 </li>
22969 </ol>
22970 </file>
22971 <file name="style.css">
22972 .odd {
22973 color: red;
22974 }
22975 .even {
22976 color: blue;
22977 }
22978 </file>
22979 <file name="protractor.js" type="protractor">
22980 it('should check ng-class-odd and ng-class-even', function() {
22981 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
22982 toMatch(/odd/);
22983 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
22984 toMatch(/even/);
22985 });
22986 </file>
22987 </example>
22988 */
22989var ngClassEvenDirective = classDirective('Even', 1);
22990
22991/**
22992 * @ngdoc directive
22993 * @name ngCloak
22994 * @restrict AC
22995 *
22996 * @description
22997 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
22998 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
22999 * directive to avoid the undesirable flicker effect caused by the html template display.
23000 *
23001 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23002 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23003 * of the browser view.
23004 *
23005 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23006 * `angular.min.js`.
23007 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23008 *
23009 * ```css
23010 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23011 * display: none !important;
23012 * }
23013 * ```
23014 *
23015 * When this css rule is loaded by the browser, all html elements (including their children) that
23016 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23017 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23018 * the compiled element visible.
23019 *
23020 * For the best result, the `angular.js` script must be loaded in the head section of the html
23021 * document; alternatively, the css rule above must be included in the external stylesheet of the
23022 * application.
23023 *
23024 * @element ANY
23025 *
23026 * @example
23027 <example>
23028 <file name="index.html">
23029 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23030 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23031 </file>
23032 <file name="protractor.js" type="protractor">
23033 it('should remove the template directive and css class', function() {
23034 expect($('#template1').getAttribute('ng-cloak')).
23035 toBeNull();
23036 expect($('#template2').getAttribute('ng-cloak')).
23037 toBeNull();
23038 });
23039 </file>
23040 </example>
23041 *
23042 */
23043var ngCloakDirective = ngDirective({
23044 compile: function(element, attr) {
23045 attr.$set('ngCloak', undefined);
23046 element.removeClass('ng-cloak');
23047 }
23048});
23049
23050/**
23051 * @ngdoc directive
23052 * @name ngController
23053 *
23054 * @description
23055 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23056 * supports the principles behind the Model-View-Controller design pattern.
23057 *
23058 * MVC components in angular:
23059 *
23060 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23061 * are accessed through bindings.
23062 * * View — The template (HTML with data bindings) that is rendered into the View.
23063 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23064 * logic behind the application to decorate the scope with functions and values
23065 *
23066 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23067 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23068 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23069 * and executed twice.
23070 *
23071 * @element ANY
23072 * @scope
23073 * @priority 500
23074 * @param {expression} ngController Name of a constructor function registered with the current
23075 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23076 * that on the current scope evaluates to a constructor function.
23077 *
23078 * The controller instance can be published into a scope property by specifying
23079 * `ng-controller="as propertyName"`.
23080 *
23081 * If the current `$controllerProvider` is configured to use globals (via
23082 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23083 * also be the name of a globally accessible constructor function (not recommended).
23084 *
23085 * @example
23086 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23087 * greeting are methods declared on the controller (see source tab). These methods can
23088 * easily be called from the angular markup. Any changes to the data are automatically reflected
23089 * in the View without the need for a manual update.
23090 *
23091 * Two different declaration styles are included below:
23092 *
23093 * * one binds methods and properties directly onto the controller using `this`:
23094 * `ng-controller="SettingsController1 as settings"`
23095 * * one injects `$scope` into the controller:
23096 * `ng-controller="SettingsController2"`
23097 *
23098 * The second option is more common in the Angular community, and is generally used in boilerplates
23099 * and in this guide. However, there are advantages to binding properties directly to the controller
23100 * and avoiding scope.
23101 *
23102 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23103 * multiple controllers apply to an element.
23104 * * If you are writing your controllers as classes you have easier access to the properties and
23105 * methods, which will appear on the scope, from inside the controller code.
23106 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23107 * inheritance masking primitives.
23108 *
23109 * This example demonstrates the `controller as` syntax.
23110 *
23111 * <example name="ngControllerAs" module="controllerAsExample">
23112 * <file name="index.html">
23113 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23114 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23115 * <button ng-click="settings.greet()">greet</button><br/>
23116 * Contact:
23117 * <ul>
23118 * <li ng-repeat="contact in settings.contacts">
23119 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23120 * <option>phone</option>
23121 * <option>email</option>
23122 * </select>
23123 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23124 * <button ng-click="settings.clearContact(contact)">clear</button>
23125 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23126 * </li>
23127 * <li><button ng-click="settings.addContact()">add</button></li>
23128 * </ul>
23129 * </div>
23130 * </file>
23131 * <file name="app.js">
23132 * angular.module('controllerAsExample', [])
23133 * .controller('SettingsController1', SettingsController1);
23134 *
23135 * function SettingsController1() {
23136 * this.name = "John Smith";
23137 * this.contacts = [
23138 * {type: 'phone', value: '408 555 1212'},
23139 * {type: 'email', value: 'john.smith@example.org'} ];
23140 * }
23141 *
23142 * SettingsController1.prototype.greet = function() {
23143 * alert(this.name);
23144 * };
23145 *
23146 * SettingsController1.prototype.addContact = function() {
23147 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23148 * };
23149 *
23150 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23151 * var index = this.contacts.indexOf(contactToRemove);
23152 * this.contacts.splice(index, 1);
23153 * };
23154 *
23155 * SettingsController1.prototype.clearContact = function(contact) {
23156 * contact.type = 'phone';
23157 * contact.value = '';
23158 * };
23159 * </file>
23160 * <file name="protractor.js" type="protractor">
23161 * it('should check controller as', function() {
23162 * var container = element(by.id('ctrl-as-exmpl'));
23163 * expect(container.element(by.model('settings.name'))
23164 * .getAttribute('value')).toBe('John Smith');
23165 *
23166 * var firstRepeat =
23167 * container.element(by.repeater('contact in settings.contacts').row(0));
23168 * var secondRepeat =
23169 * container.element(by.repeater('contact in settings.contacts').row(1));
23170 *
23171 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23172 * .toBe('408 555 1212');
23173 *
23174 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23175 * .toBe('john.smith@example.org');
23176 *
23177 * firstRepeat.element(by.buttonText('clear')).click();
23178 *
23179 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23180 * .toBe('');
23181 *
23182 * container.element(by.buttonText('add')).click();
23183 *
23184 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23185 * .element(by.model('contact.value'))
23186 * .getAttribute('value'))
23187 * .toBe('yourname@example.org');
23188 * });
23189 * </file>
23190 * </example>
23191 *
23192 * This example demonstrates the "attach to `$scope`" style of controller.
23193 *
23194 * <example name="ngController" module="controllerExample">
23195 * <file name="index.html">
23196 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23197 * <label>Name: <input type="text" ng-model="name"/></label>
23198 * <button ng-click="greet()">greet</button><br/>
23199 * Contact:
23200 * <ul>
23201 * <li ng-repeat="contact in contacts">
23202 * <select ng-model="contact.type" id="select_{{$index}}">
23203 * <option>phone</option>
23204 * <option>email</option>
23205 * </select>
23206 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23207 * <button ng-click="clearContact(contact)">clear</button>
23208 * <button ng-click="removeContact(contact)">X</button>
23209 * </li>
23210 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23211 * </ul>
23212 * </div>
23213 * </file>
23214 * <file name="app.js">
23215 * angular.module('controllerExample', [])
23216 * .controller('SettingsController2', ['$scope', SettingsController2]);
23217 *
23218 * function SettingsController2($scope) {
23219 * $scope.name = "John Smith";
23220 * $scope.contacts = [
23221 * {type:'phone', value:'408 555 1212'},
23222 * {type:'email', value:'john.smith@example.org'} ];
23223 *
23224 * $scope.greet = function() {
23225 * alert($scope.name);
23226 * };
23227 *
23228 * $scope.addContact = function() {
23229 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23230 * };
23231 *
23232 * $scope.removeContact = function(contactToRemove) {
23233 * var index = $scope.contacts.indexOf(contactToRemove);
23234 * $scope.contacts.splice(index, 1);
23235 * };
23236 *
23237 * $scope.clearContact = function(contact) {
23238 * contact.type = 'phone';
23239 * contact.value = '';
23240 * };
23241 * }
23242 * </file>
23243 * <file name="protractor.js" type="protractor">
23244 * it('should check controller', function() {
23245 * var container = element(by.id('ctrl-exmpl'));
23246 *
23247 * expect(container.element(by.model('name'))
23248 * .getAttribute('value')).toBe('John Smith');
23249 *
23250 * var firstRepeat =
23251 * container.element(by.repeater('contact in contacts').row(0));
23252 * var secondRepeat =
23253 * container.element(by.repeater('contact in contacts').row(1));
23254 *
23255 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23256 * .toBe('408 555 1212');
23257 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23258 * .toBe('john.smith@example.org');
23259 *
23260 * firstRepeat.element(by.buttonText('clear')).click();
23261 *
23262 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23263 * .toBe('');
23264 *
23265 * container.element(by.buttonText('add')).click();
23266 *
23267 * expect(container.element(by.repeater('contact in contacts').row(2))
23268 * .element(by.model('contact.value'))
23269 * .getAttribute('value'))
23270 * .toBe('yourname@example.org');
23271 * });
23272 * </file>
23273 *</example>
23274
23275 */
23276var ngControllerDirective = [function() {
23277 return {
23278 restrict: 'A',
23279 scope: true,
23280 controller: '@',
23281 priority: 500
23282 };
23283}];
23284
23285/**
23286 * @ngdoc directive
23287 * @name ngCsp
23288 *
23289 * @element html
23290 * @description
23291 *
23292 * Angular has some features that can break certain
23293 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23294 *
23295 * If you intend to implement these rules then you must tell Angular not to use these features.
23296 *
23297 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23298 *
23299 *
23300 * The following rules affect Angular:
23301 *
23302 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23303 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23304 * increase in the speed of evaluating Angular expressions.
23305 *
23306 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23307 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23308 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23309 * `angular-csp.css` in your HTML manually.
23310 *
23311 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23312 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23313 * however, triggers a CSP error to be logged in the console:
23314 *
23315 * ```
23316 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23317 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23318 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23319 * ```
23320 *
23321 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23322 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23323 * the `angular.js` file.
23324 *
23325 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23326 *
23327 * You can specify which of the CSP related Angular features should be deactivated by providing
23328 * a value for the `ng-csp` attribute. The options are as follows:
23329 *
23330 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23331 *
23332 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23333 *
23334 * You can use these values in the following combinations:
23335 *
23336 *
23337 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23338 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23339 * of Angular.
23340 *
23341 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23342 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23343 * of Angular.
23344 *
23345 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23346 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23347 *
23348 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23349 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23350 *
23351 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23352 * styles nor use eval, which is the same as an empty: ng-csp.
23353 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23354 *
23355 * @example
23356 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23357 ```html
23358 <!doctype html>
23359 <html ng-app ng-csp>
23360 ...
23361 ...
23362 </html>
23363 ```
23364 * @example
23365 // Note: the suffix `.csp` in the example name triggers
23366 // csp mode in our http server!
23367 <example name="example.csp" module="cspExample" ng-csp="true">
23368 <file name="index.html">
23369 <div ng-controller="MainController as ctrl">
23370 <div>
23371 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23372 <span id="counter">
23373 {{ctrl.counter}}
23374 </span>
23375 </div>
23376
23377 <div>
23378 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23379 <span id="evilError">
23380 {{ctrl.evilError}}
23381 </span>
23382 </div>
23383 </div>
23384 </file>
23385 <file name="script.js">
23386 angular.module('cspExample', [])
23387 .controller('MainController', function() {
23388 this.counter = 0;
23389 this.inc = function() {
23390 this.counter++;
23391 };
23392 this.evil = function() {
23393 // jshint evil:true
23394 try {
23395 eval('1+2');
23396 } catch (e) {
23397 this.evilError = e.message;
23398 }
23399 };
23400 });
23401 </file>
23402 <file name="protractor.js" type="protractor">
23403 var util, webdriver;
23404
23405 var incBtn = element(by.id('inc'));
23406 var counter = element(by.id('counter'));
23407 var evilBtn = element(by.id('evil'));
23408 var evilError = element(by.id('evilError'));
23409
23410 function getAndClearSevereErrors() {
23411 return browser.manage().logs().get('browser').then(function(browserLog) {
23412 return browserLog.filter(function(logEntry) {
23413 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23414 });
23415 });
23416 }
23417
23418 function clearErrors() {
23419 getAndClearSevereErrors();
23420 }
23421
23422 function expectNoErrors() {
23423 getAndClearSevereErrors().then(function(filteredLog) {
23424 expect(filteredLog.length).toEqual(0);
23425 if (filteredLog.length) {
23426 console.log('browser console errors: ' + util.inspect(filteredLog));
23427 }
23428 });
23429 }
23430
23431 function expectError(regex) {
23432 getAndClearSevereErrors().then(function(filteredLog) {
23433 var found = false;
23434 filteredLog.forEach(function(log) {
23435 if (log.message.match(regex)) {
23436 found = true;
23437 }
23438 });
23439 if (!found) {
23440 throw new Error('expected an error that matches ' + regex);
23441 }
23442 });
23443 }
23444
23445 beforeEach(function() {
23446 util = require('util');
23447 webdriver = require('protractor/node_modules/selenium-webdriver');
23448 });
23449
23450 // For now, we only test on Chrome,
23451 // as Safari does not load the page with Protractor's injected scripts,
23452 // and Firefox webdriver always disables content security policy (#6358)
23453 if (browser.params.browser !== 'chrome') {
23454 return;
23455 }
23456
23457 it('should not report errors when the page is loaded', function() {
23458 // clear errors so we are not dependent on previous tests
23459 clearErrors();
23460 // Need to reload the page as the page is already loaded when
23461 // we come here
23462 browser.driver.getCurrentUrl().then(function(url) {
23463 browser.get(url);
23464 });
23465 expectNoErrors();
23466 });
23467
23468 it('should evaluate expressions', function() {
23469 expect(counter.getText()).toEqual('0');
23470 incBtn.click();
23471 expect(counter.getText()).toEqual('1');
23472 expectNoErrors();
23473 });
23474
23475 it('should throw and report an error when using "eval"', function() {
23476 evilBtn.click();
23477 expect(evilError.getText()).toMatch(/Content Security Policy/);
23478 expectError(/Content Security Policy/);
23479 });
23480 </file>
23481 </example>
23482 */
23483
23484// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23485// bootstrap the system (before $parse is instantiated), for this reason we just have
23486// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23487
23488/**
23489 * @ngdoc directive
23490 * @name ngClick
23491 *
23492 * @description
23493 * The ngClick directive allows you to specify custom behavior when
23494 * an element is clicked.
23495 *
23496 * @element ANY
23497 * @priority 0
23498 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23499 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23500 *
23501 * @example
23502 <example>
23503 <file name="index.html">
23504 <button ng-click="count = count + 1" ng-init="count=0">
23505 Increment
23506 </button>
23507 <span>
23508 count: {{count}}
23509 </span>
23510 </file>
23511 <file name="protractor.js" type="protractor">
23512 it('should check ng-click', function() {
23513 expect(element(by.binding('count')).getText()).toMatch('0');
23514 element(by.css('button')).click();
23515 expect(element(by.binding('count')).getText()).toMatch('1');
23516 });
23517 </file>
23518 </example>
23519 */
23520/*
23521 * A collection of directives that allows creation of custom event handlers that are defined as
23522 * angular expressions and are compiled and executed within the current scope.
23523 */
23524var ngEventDirectives = {};
23525
23526// For events that might fire synchronously during DOM manipulation
23527// we need to execute their event handlers asynchronously using $evalAsync,
23528// so that they are not executed in an inconsistent state.
23529var forceAsyncEvents = {
23530 'blur': true,
23531 'focus': true
23532};
23533forEach(
23534 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23535 function(eventName) {
23536 var directiveName = directiveNormalize('ng-' + eventName);
23537 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23538 return {
23539 restrict: 'A',
23540 compile: function($element, attr) {
23541 // We expose the powerful $event object on the scope that provides access to the Window,
23542 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23543 // checks at the cost of speed since event handler expressions are not executed as
23544 // frequently as regular change detection.
23545 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23546 return function ngEventHandler(scope, element) {
23547 element.on(eventName, function(event) {
23548 var callback = function() {
23549 fn(scope, {$event:event});
23550 };
23551 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23552 scope.$evalAsync(callback);
23553 } else {
23554 scope.$apply(callback);
23555 }
23556 });
23557 };
23558 }
23559 };
23560 }];
23561 }
23562);
23563
23564/**
23565 * @ngdoc directive
23566 * @name ngDblclick
23567 *
23568 * @description
23569 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23570 *
23571 * @element ANY
23572 * @priority 0
23573 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23574 * a dblclick. (The Event object is available as `$event`)
23575 *
23576 * @example
23577 <example>
23578 <file name="index.html">
23579 <button ng-dblclick="count = count + 1" ng-init="count=0">
23580 Increment (on double click)
23581 </button>
23582 count: {{count}}
23583 </file>
23584 </example>
23585 */
23586
23587
23588/**
23589 * @ngdoc directive
23590 * @name ngMousedown
23591 *
23592 * @description
23593 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23594 *
23595 * @element ANY
23596 * @priority 0
23597 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23598 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23599 *
23600 * @example
23601 <example>
23602 <file name="index.html">
23603 <button ng-mousedown="count = count + 1" ng-init="count=0">
23604 Increment (on mouse down)
23605 </button>
23606 count: {{count}}
23607 </file>
23608 </example>
23609 */
23610
23611
23612/**
23613 * @ngdoc directive
23614 * @name ngMouseup
23615 *
23616 * @description
23617 * Specify custom behavior on mouseup event.
23618 *
23619 * @element ANY
23620 * @priority 0
23621 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23622 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23623 *
23624 * @example
23625 <example>
23626 <file name="index.html">
23627 <button ng-mouseup="count = count + 1" ng-init="count=0">
23628 Increment (on mouse up)
23629 </button>
23630 count: {{count}}
23631 </file>
23632 </example>
23633 */
23634
23635/**
23636 * @ngdoc directive
23637 * @name ngMouseover
23638 *
23639 * @description
23640 * Specify custom behavior on mouseover event.
23641 *
23642 * @element ANY
23643 * @priority 0
23644 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23645 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23646 *
23647 * @example
23648 <example>
23649 <file name="index.html">
23650 <button ng-mouseover="count = count + 1" ng-init="count=0">
23651 Increment (when mouse is over)
23652 </button>
23653 count: {{count}}
23654 </file>
23655 </example>
23656 */
23657
23658
23659/**
23660 * @ngdoc directive
23661 * @name ngMouseenter
23662 *
23663 * @description
23664 * Specify custom behavior on mouseenter event.
23665 *
23666 * @element ANY
23667 * @priority 0
23668 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23669 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23670 *
23671 * @example
23672 <example>
23673 <file name="index.html">
23674 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23675 Increment (when mouse enters)
23676 </button>
23677 count: {{count}}
23678 </file>
23679 </example>
23680 */
23681
23682
23683/**
23684 * @ngdoc directive
23685 * @name ngMouseleave
23686 *
23687 * @description
23688 * Specify custom behavior on mouseleave event.
23689 *
23690 * @element ANY
23691 * @priority 0
23692 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23693 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23694 *
23695 * @example
23696 <example>
23697 <file name="index.html">
23698 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23699 Increment (when mouse leaves)
23700 </button>
23701 count: {{count}}
23702 </file>
23703 </example>
23704 */
23705
23706
23707/**
23708 * @ngdoc directive
23709 * @name ngMousemove
23710 *
23711 * @description
23712 * Specify custom behavior on mousemove event.
23713 *
23714 * @element ANY
23715 * @priority 0
23716 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23717 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23718 *
23719 * @example
23720 <example>
23721 <file name="index.html">
23722 <button ng-mousemove="count = count + 1" ng-init="count=0">
23723 Increment (when mouse moves)
23724 </button>
23725 count: {{count}}
23726 </file>
23727 </example>
23728 */
23729
23730
23731/**
23732 * @ngdoc directive
23733 * @name ngKeydown
23734 *
23735 * @description
23736 * Specify custom behavior on keydown event.
23737 *
23738 * @element ANY
23739 * @priority 0
23740 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23741 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23742 *
23743 * @example
23744 <example>
23745 <file name="index.html">
23746 <input ng-keydown="count = count + 1" ng-init="count=0">
23747 key down count: {{count}}
23748 </file>
23749 </example>
23750 */
23751
23752
23753/**
23754 * @ngdoc directive
23755 * @name ngKeyup
23756 *
23757 * @description
23758 * Specify custom behavior on keyup event.
23759 *
23760 * @element ANY
23761 * @priority 0
23762 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23763 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23764 *
23765 * @example
23766 <example>
23767 <file name="index.html">
23768 <p>Typing in the input box below updates the key count</p>
23769 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23770
23771 <p>Typing in the input box below updates the keycode</p>
23772 <input ng-keyup="event=$event">
23773 <p>event keyCode: {{ event.keyCode }}</p>
23774 <p>event altKey: {{ event.altKey }}</p>
23775 </file>
23776 </example>
23777 */
23778
23779
23780/**
23781 * @ngdoc directive
23782 * @name ngKeypress
23783 *
23784 * @description
23785 * Specify custom behavior on keypress event.
23786 *
23787 * @element ANY
23788 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23789 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23790 * and can be interrogated for keyCode, altKey, etc.)
23791 *
23792 * @example
23793 <example>
23794 <file name="index.html">
23795 <input ng-keypress="count = count + 1" ng-init="count=0">
23796 key press count: {{count}}
23797 </file>
23798 </example>
23799 */
23800
23801
23802/**
23803 * @ngdoc directive
23804 * @name ngSubmit
23805 *
23806 * @description
23807 * Enables binding angular expressions to onsubmit events.
23808 *
23809 * Additionally it prevents the default action (which for form means sending the request to the
23810 * server and reloading the current page), but only if the form does not contain `action`,
23811 * `data-action`, or `x-action` attributes.
23812 *
23813 * <div class="alert alert-warning">
23814 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23815 * `ngSubmit` handlers together. See the
23816 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23817 * for a detailed discussion of when `ngSubmit` may be triggered.
23818 * </div>
23819 *
23820 * @element form
23821 * @priority 0
23822 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23823 * ({@link guide/expression#-event- Event object is available as `$event`})
23824 *
23825 * @example
23826 <example module="submitExample">
23827 <file name="index.html">
23828 <script>
23829 angular.module('submitExample', [])
23830 .controller('ExampleController', ['$scope', function($scope) {
23831 $scope.list = [];
23832 $scope.text = 'hello';
23833 $scope.submit = function() {
23834 if ($scope.text) {
23835 $scope.list.push(this.text);
23836 $scope.text = '';
23837 }
23838 };
23839 }]);
23840 </script>
23841 <form ng-submit="submit()" ng-controller="ExampleController">
23842 Enter text and hit enter:
23843 <input type="text" ng-model="text" name="text" />
23844 <input type="submit" id="submit" value="Submit" />
23845 <pre>list={{list}}</pre>
23846 </form>
23847 </file>
23848 <file name="protractor.js" type="protractor">
23849 it('should check ng-submit', function() {
23850 expect(element(by.binding('list')).getText()).toBe('list=[]');
23851 element(by.css('#submit')).click();
23852 expect(element(by.binding('list')).getText()).toContain('hello');
23853 expect(element(by.model('text')).getAttribute('value')).toBe('');
23854 });
23855 it('should ignore empty strings', function() {
23856 expect(element(by.binding('list')).getText()).toBe('list=[]');
23857 element(by.css('#submit')).click();
23858 element(by.css('#submit')).click();
23859 expect(element(by.binding('list')).getText()).toContain('hello');
23860 });
23861 </file>
23862 </example>
23863 */
23864
23865/**
23866 * @ngdoc directive
23867 * @name ngFocus
23868 *
23869 * @description
23870 * Specify custom behavior on focus event.
23871 *
23872 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
23873 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
23874 * during an `$apply` to ensure a consistent state.
23875 *
23876 * @element window, input, select, textarea, a
23877 * @priority 0
23878 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
23879 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
23880 *
23881 * @example
23882 * See {@link ng.directive:ngClick ngClick}
23883 */
23884
23885/**
23886 * @ngdoc directive
23887 * @name ngBlur
23888 *
23889 * @description
23890 * Specify custom behavior on blur event.
23891 *
23892 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
23893 * an element has lost focus.
23894 *
23895 * Note: As the `blur` event is executed synchronously also during DOM manipulations
23896 * (e.g. removing a focussed input),
23897 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
23898 * during an `$apply` to ensure a consistent state.
23899 *
23900 * @element window, input, select, textarea, a
23901 * @priority 0
23902 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
23903 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
23904 *
23905 * @example
23906 * See {@link ng.directive:ngClick ngClick}
23907 */
23908
23909/**
23910 * @ngdoc directive
23911 * @name ngCopy
23912 *
23913 * @description
23914 * Specify custom behavior on copy event.
23915 *
23916 * @element window, input, select, textarea, a
23917 * @priority 0
23918 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
23919 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
23920 *
23921 * @example
23922 <example>
23923 <file name="index.html">
23924 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
23925 copied: {{copied}}
23926 </file>
23927 </example>
23928 */
23929
23930/**
23931 * @ngdoc directive
23932 * @name ngCut
23933 *
23934 * @description
23935 * Specify custom behavior on cut event.
23936 *
23937 * @element window, input, select, textarea, a
23938 * @priority 0
23939 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
23940 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
23941 *
23942 * @example
23943 <example>
23944 <file name="index.html">
23945 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
23946 cut: {{cut}}
23947 </file>
23948 </example>
23949 */
23950
23951/**
23952 * @ngdoc directive
23953 * @name ngPaste
23954 *
23955 * @description
23956 * Specify custom behavior on paste event.
23957 *
23958 * @element window, input, select, textarea, a
23959 * @priority 0
23960 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
23961 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
23962 *
23963 * @example
23964 <example>
23965 <file name="index.html">
23966 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
23967 pasted: {{paste}}
23968 </file>
23969 </example>
23970 */
23971
23972/**
23973 * @ngdoc directive
23974 * @name ngIf
23975 * @restrict A
23976 * @multiElement
23977 *
23978 * @description
23979 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
23980 * {expression}. If the expression assigned to `ngIf` evaluates to a false
23981 * value then the element is removed from the DOM, otherwise a clone of the
23982 * element is reinserted into the DOM.
23983 *
23984 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
23985 * element in the DOM rather than changing its visibility via the `display` css property. A common
23986 * case when this difference is significant is when using css selectors that rely on an element's
23987 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
23988 *
23989 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
23990 * is created when the element is restored. The scope created within `ngIf` inherits from
23991 * its parent scope using
23992 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
23993 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
23994 * a javascript primitive defined in the parent scope. In this case any modifications made to the
23995 * variable within the child scope will override (hide) the value in the parent scope.
23996 *
23997 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
23998 * is if an element's class attribute is directly modified after it's compiled, using something like
23999 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24000 * the added class will be lost because the original compiled state is used to regenerate the element.
24001 *
24002 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24003 * and `leave` effects.
24004 *
24005 * @animations
24006 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24007 * leave - happens just before the `ngIf` contents are removed from the DOM
24008 *
24009 * @element ANY
24010 * @scope
24011 * @priority 600
24012 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24013 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24014 * element is added to the DOM tree.
24015 *
24016 * @example
24017 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24018 <file name="index.html">
24019 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24020 Show when checked:
24021 <span ng-if="checked" class="animate-if">
24022 This is removed when the checkbox is unchecked.
24023 </span>
24024 </file>
24025 <file name="animations.css">
24026 .animate-if {
24027 background:white;
24028 border:1px solid black;
24029 padding:10px;
24030 }
24031
24032 .animate-if.ng-enter, .animate-if.ng-leave {
24033 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24034 }
24035
24036 .animate-if.ng-enter,
24037 .animate-if.ng-leave.ng-leave-active {
24038 opacity:0;
24039 }
24040
24041 .animate-if.ng-leave,
24042 .animate-if.ng-enter.ng-enter-active {
24043 opacity:1;
24044 }
24045 </file>
24046 </example>
24047 */
24048var ngIfDirective = ['$animate', function($animate) {
24049 return {
24050 multiElement: true,
24051 transclude: 'element',
24052 priority: 600,
24053 terminal: true,
24054 restrict: 'A',
24055 $$tlb: true,
24056 link: function($scope, $element, $attr, ctrl, $transclude) {
24057 var block, childScope, previousElements;
24058 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24059
24060 if (value) {
24061 if (!childScope) {
24062 $transclude(function(clone, newScope) {
24063 childScope = newScope;
24064 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24065 // Note: We only need the first/last node of the cloned nodes.
24066 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24067 // by a directive with templateUrl when its template arrives.
24068 block = {
24069 clone: clone
24070 };
24071 $animate.enter(clone, $element.parent(), $element);
24072 });
24073 }
24074 } else {
24075 if (previousElements) {
24076 previousElements.remove();
24077 previousElements = null;
24078 }
24079 if (childScope) {
24080 childScope.$destroy();
24081 childScope = null;
24082 }
24083 if (block) {
24084 previousElements = getBlockNodes(block.clone);
24085 $animate.leave(previousElements).then(function() {
24086 previousElements = null;
24087 });
24088 block = null;
24089 }
24090 }
24091 });
24092 }
24093 };
24094}];
24095
24096/**
24097 * @ngdoc directive
24098 * @name ngInclude
24099 * @restrict ECA
24100 *
24101 * @description
24102 * Fetches, compiles and includes an external HTML fragment.
24103 *
24104 * By default, the template URL is restricted to the same domain and protocol as the
24105 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24106 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24107 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24108 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24109 * ng.$sce Strict Contextual Escaping}.
24110 *
24111 * In addition, the browser's
24112 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24113 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24114 * policy may further restrict whether the template is successfully loaded.
24115 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24116 * access on some browsers.
24117 *
24118 * @animations
24119 * enter - animation is used to bring new content into the browser.
24120 * leave - animation is used to animate existing content away.
24121 *
24122 * The enter and leave animation occur concurrently.
24123 *
24124 * @scope
24125 * @priority 400
24126 *
24127 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24128 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24129 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24130 *
24131 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24132 * $anchorScroll} to scroll the viewport after the content is loaded.
24133 *
24134 * - If the attribute is not set, disable scrolling.
24135 * - If the attribute is set without value, enable scrolling.
24136 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24137 *
24138 * @example
24139 <example module="includeExample" deps="angular-animate.js" animations="true">
24140 <file name="index.html">
24141 <div ng-controller="ExampleController">
24142 <select ng-model="template" ng-options="t.name for t in templates">
24143 <option value="">(blank)</option>
24144 </select>
24145 url of the template: <code>{{template.url}}</code>
24146 <hr/>
24147 <div class="slide-animate-container">
24148 <div class="slide-animate" ng-include="template.url"></div>
24149 </div>
24150 </div>
24151 </file>
24152 <file name="script.js">
24153 angular.module('includeExample', ['ngAnimate'])
24154 .controller('ExampleController', ['$scope', function($scope) {
24155 $scope.templates =
24156 [ { name: 'template1.html', url: 'template1.html'},
24157 { name: 'template2.html', url: 'template2.html'} ];
24158 $scope.template = $scope.templates[0];
24159 }]);
24160 </file>
24161 <file name="template1.html">
24162 Content of template1.html
24163 </file>
24164 <file name="template2.html">
24165 Content of template2.html
24166 </file>
24167 <file name="animations.css">
24168 .slide-animate-container {
24169 position:relative;
24170 background:white;
24171 border:1px solid black;
24172 height:40px;
24173 overflow:hidden;
24174 }
24175
24176 .slide-animate {
24177 padding:10px;
24178 }
24179
24180 .slide-animate.ng-enter, .slide-animate.ng-leave {
24181 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24182
24183 position:absolute;
24184 top:0;
24185 left:0;
24186 right:0;
24187 bottom:0;
24188 display:block;
24189 padding:10px;
24190 }
24191
24192 .slide-animate.ng-enter {
24193 top:-50px;
24194 }
24195 .slide-animate.ng-enter.ng-enter-active {
24196 top:0;
24197 }
24198
24199 .slide-animate.ng-leave {
24200 top:0;
24201 }
24202 .slide-animate.ng-leave.ng-leave-active {
24203 top:50px;
24204 }
24205 </file>
24206 <file name="protractor.js" type="protractor">
24207 var templateSelect = element(by.model('template'));
24208 var includeElem = element(by.css('[ng-include]'));
24209
24210 it('should load template1.html', function() {
24211 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24212 });
24213
24214 it('should load template2.html', function() {
24215 if (browser.params.browser == 'firefox') {
24216 // Firefox can't handle using selects
24217 // See https://github.com/angular/protractor/issues/480
24218 return;
24219 }
24220 templateSelect.click();
24221 templateSelect.all(by.css('option')).get(2).click();
24222 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24223 });
24224
24225 it('should change to blank', function() {
24226 if (browser.params.browser == 'firefox') {
24227 // Firefox can't handle using selects
24228 return;
24229 }
24230 templateSelect.click();
24231 templateSelect.all(by.css('option')).get(0).click();
24232 expect(includeElem.isPresent()).toBe(false);
24233 });
24234 </file>
24235 </example>
24236 */
24237
24238
24239/**
24240 * @ngdoc event
24241 * @name ngInclude#$includeContentRequested
24242 * @eventType emit on the scope ngInclude was declared in
24243 * @description
24244 * Emitted every time the ngInclude content is requested.
24245 *
24246 * @param {Object} angularEvent Synthetic event object.
24247 * @param {String} src URL of content to load.
24248 */
24249
24250
24251/**
24252 * @ngdoc event
24253 * @name ngInclude#$includeContentLoaded
24254 * @eventType emit on the current ngInclude scope
24255 * @description
24256 * Emitted every time the ngInclude content is reloaded.
24257 *
24258 * @param {Object} angularEvent Synthetic event object.
24259 * @param {String} src URL of content to load.
24260 */
24261
24262
24263/**
24264 * @ngdoc event
24265 * @name ngInclude#$includeContentError
24266 * @eventType emit on the scope ngInclude was declared in
24267 * @description
24268 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24269 *
24270 * @param {Object} angularEvent Synthetic event object.
24271 * @param {String} src URL of content to load.
24272 */
24273var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24274 function($templateRequest, $anchorScroll, $animate) {
24275 return {
24276 restrict: 'ECA',
24277 priority: 400,
24278 terminal: true,
24279 transclude: 'element',
24280 controller: angular.noop,
24281 compile: function(element, attr) {
24282 var srcExp = attr.ngInclude || attr.src,
24283 onloadExp = attr.onload || '',
24284 autoScrollExp = attr.autoscroll;
24285
24286 return function(scope, $element, $attr, ctrl, $transclude) {
24287 var changeCounter = 0,
24288 currentScope,
24289 previousElement,
24290 currentElement;
24291
24292 var cleanupLastIncludeContent = function() {
24293 if (previousElement) {
24294 previousElement.remove();
24295 previousElement = null;
24296 }
24297 if (currentScope) {
24298 currentScope.$destroy();
24299 currentScope = null;
24300 }
24301 if (currentElement) {
24302 $animate.leave(currentElement).then(function() {
24303 previousElement = null;
24304 });
24305 previousElement = currentElement;
24306 currentElement = null;
24307 }
24308 };
24309
24310 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24311 var afterAnimation = function() {
24312 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24313 $anchorScroll();
24314 }
24315 };
24316 var thisChangeId = ++changeCounter;
24317
24318 if (src) {
24319 //set the 2nd param to true to ignore the template request error so that the inner
24320 //contents and scope can be cleaned up.
24321 $templateRequest(src, true).then(function(response) {
24322 if (thisChangeId !== changeCounter) return;
24323 var newScope = scope.$new();
24324 ctrl.template = response;
24325
24326 // Note: This will also link all children of ng-include that were contained in the original
24327 // html. If that content contains controllers, ... they could pollute/change the scope.
24328 // However, using ng-include on an element with additional content does not make sense...
24329 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24330 // function is called before linking the content, which would apply child
24331 // directives to non existing elements.
24332 var clone = $transclude(newScope, function(clone) {
24333 cleanupLastIncludeContent();
24334 $animate.enter(clone, null, $element).then(afterAnimation);
24335 });
24336
24337 currentScope = newScope;
24338 currentElement = clone;
24339
24340 currentScope.$emit('$includeContentLoaded', src);
24341 scope.$eval(onloadExp);
24342 }, function() {
24343 if (thisChangeId === changeCounter) {
24344 cleanupLastIncludeContent();
24345 scope.$emit('$includeContentError', src);
24346 }
24347 });
24348 scope.$emit('$includeContentRequested', src);
24349 } else {
24350 cleanupLastIncludeContent();
24351 ctrl.template = null;
24352 }
24353 });
24354 };
24355 }
24356 };
24357}];
24358
24359// This directive is called during the $transclude call of the first `ngInclude` directive.
24360// It will replace and compile the content of the element with the loaded template.
24361// We need this directive so that the element content is already filled when
24362// the link function of another directive on the same element as ngInclude
24363// is called.
24364var ngIncludeFillContentDirective = ['$compile',
24365 function($compile) {
24366 return {
24367 restrict: 'ECA',
24368 priority: -400,
24369 require: 'ngInclude',
24370 link: function(scope, $element, $attr, ctrl) {
24371 if (/SVG/.test($element[0].toString())) {
24372 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24373 // support innerHTML, so detect this here and try to generate the contents
24374 // specially.
24375 $element.empty();
24376 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24377 function namespaceAdaptedClone(clone) {
24378 $element.append(clone);
24379 }, {futureParentElement: $element});
24380 return;
24381 }
24382
24383 $element.html(ctrl.template);
24384 $compile($element.contents())(scope);
24385 }
24386 };
24387 }];
24388
24389/**
24390 * @ngdoc directive
24391 * @name ngInit
24392 * @restrict AC
24393 *
24394 * @description
24395 * The `ngInit` directive allows you to evaluate an expression in the
24396 * current scope.
24397 *
24398 * <div class="alert alert-danger">
24399 * This directive can be abused to add unnecessary amounts of logic into your templates.
24400 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24401 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24402 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24403 * rather than `ngInit` to initialize values on a scope.
24404 * </div>
24405 *
24406 * <div class="alert alert-warning">
24407 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24408 * sure you have parentheses to ensure correct operator precedence:
24409 * <pre class="prettyprint">
24410 * `<div ng-init="test1 = ($index | toString)"></div>`
24411 * </pre>
24412 * </div>
24413 *
24414 * @priority 450
24415 *
24416 * @element ANY
24417 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24418 *
24419 * @example
24420 <example module="initExample">
24421 <file name="index.html">
24422 <script>
24423 angular.module('initExample', [])
24424 .controller('ExampleController', ['$scope', function($scope) {
24425 $scope.list = [['a', 'b'], ['c', 'd']];
24426 }]);
24427 </script>
24428 <div ng-controller="ExampleController">
24429 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24430 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24431 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24432 </div>
24433 </div>
24434 </div>
24435 </file>
24436 <file name="protractor.js" type="protractor">
24437 it('should alias index positions', function() {
24438 var elements = element.all(by.css('.example-init'));
24439 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24440 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24441 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24442 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24443 });
24444 </file>
24445 </example>
24446 */
24447var ngInitDirective = ngDirective({
24448 priority: 450,
24449 compile: function() {
24450 return {
24451 pre: function(scope, element, attrs) {
24452 scope.$eval(attrs.ngInit);
24453 }
24454 };
24455 }
24456});
24457
24458/**
24459 * @ngdoc directive
24460 * @name ngList
24461 *
24462 * @description
24463 * Text input that converts between a delimited string and an array of strings. The default
24464 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24465 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24466 *
24467 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24468 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24469 * list item is respected. This implies that the user of the directive is responsible for
24470 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24471 * tab or newline character.
24472 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24473 * when joining the list items back together) and whitespace around each list item is stripped
24474 * before it is added to the model.
24475 *
24476 * ### Example with Validation
24477 *
24478 * <example name="ngList-directive" module="listExample">
24479 * <file name="app.js">
24480 * angular.module('listExample', [])
24481 * .controller('ExampleController', ['$scope', function($scope) {
24482 * $scope.names = ['morpheus', 'neo', 'trinity'];
24483 * }]);
24484 * </file>
24485 * <file name="index.html">
24486 * <form name="myForm" ng-controller="ExampleController">
24487 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24488 * <span role="alert">
24489 * <span class="error" ng-show="myForm.namesInput.$error.required">
24490 * Required!</span>
24491 * </span>
24492 * <br>
24493 * <tt>names = {{names}}</tt><br/>
24494 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24495 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24496 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24497 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24498 * </form>
24499 * </file>
24500 * <file name="protractor.js" type="protractor">
24501 * var listInput = element(by.model('names'));
24502 * var names = element(by.exactBinding('names'));
24503 * var valid = element(by.binding('myForm.namesInput.$valid'));
24504 * var error = element(by.css('span.error'));
24505 *
24506 * it('should initialize to model', function() {
24507 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24508 * expect(valid.getText()).toContain('true');
24509 * expect(error.getCssValue('display')).toBe('none');
24510 * });
24511 *
24512 * it('should be invalid if empty', function() {
24513 * listInput.clear();
24514 * listInput.sendKeys('');
24515 *
24516 * expect(names.getText()).toContain('');
24517 * expect(valid.getText()).toContain('false');
24518 * expect(error.getCssValue('display')).not.toBe('none');
24519 * });
24520 * </file>
24521 * </example>
24522 *
24523 * ### Example - splitting on newline
24524 * <example name="ngList-directive-newlines">
24525 * <file name="index.html">
24526 * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
24527 * <pre>{{ list | json }}</pre>
24528 * </file>
24529 * <file name="protractor.js" type="protractor">
24530 * it("should split the text by newlines", function() {
24531 * var listInput = element(by.model('list'));
24532 * var output = element(by.binding('list | json'));
24533 * listInput.sendKeys('abc\ndef\nghi');
24534 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24535 * });
24536 * </file>
24537 * </example>
24538 *
24539 * @element input
24540 * @param {string=} ngList optional delimiter that should be used to split the value.
24541 */
24542var ngListDirective = function() {
24543 return {
24544 restrict: 'A',
24545 priority: 100,
24546 require: 'ngModel',
24547 link: function(scope, element, attr, ctrl) {
24548 // We want to control whitespace trimming so we use this convoluted approach
24549 // to access the ngList attribute, which doesn't pre-trim the attribute
24550 var ngList = element.attr(attr.$attr.ngList) || ', ';
24551 var trimValues = attr.ngTrim !== 'false';
24552 var separator = trimValues ? trim(ngList) : ngList;
24553
24554 var parse = function(viewValue) {
24555 // If the viewValue is invalid (say required but empty) it will be `undefined`
24556 if (isUndefined(viewValue)) return;
24557
24558 var list = [];
24559
24560 if (viewValue) {
24561 forEach(viewValue.split(separator), function(value) {
24562 if (value) list.push(trimValues ? trim(value) : value);
24563 });
24564 }
24565
24566 return list;
24567 };
24568
24569 ctrl.$parsers.push(parse);
24570 ctrl.$formatters.push(function(value) {
24571 if (isArray(value)) {
24572 return value.join(ngList);
24573 }
24574
24575 return undefined;
24576 });
24577
24578 // Override the standard $isEmpty because an empty array means the input is empty.
24579 ctrl.$isEmpty = function(value) {
24580 return !value || !value.length;
24581 };
24582 }
24583 };
24584};
24585
24586/* global VALID_CLASS: true,
24587 INVALID_CLASS: true,
24588 PRISTINE_CLASS: true,
24589 DIRTY_CLASS: true,
24590 UNTOUCHED_CLASS: true,
24591 TOUCHED_CLASS: true,
24592*/
24593
24594var VALID_CLASS = 'ng-valid',
24595 INVALID_CLASS = 'ng-invalid',
24596 PRISTINE_CLASS = 'ng-pristine',
24597 DIRTY_CLASS = 'ng-dirty',
24598 UNTOUCHED_CLASS = 'ng-untouched',
24599 TOUCHED_CLASS = 'ng-touched',
24600 PENDING_CLASS = 'ng-pending';
24601
24602var ngModelMinErr = minErr('ngModel');
24603
24604/**
24605 * @ngdoc type
24606 * @name ngModel.NgModelController
24607 *
24608 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24609 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24610 * is set.
24611 * @property {*} $modelValue The value in the model that the control is bound to.
24612 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24613 the control reads value from the DOM. The functions are called in array order, each passing
24614 its return value through to the next. The last return value is forwarded to the
24615 {@link ngModel.NgModelController#$validators `$validators`} collection.
24616
24617Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24618`$viewValue`}.
24619
24620Returning `undefined` from a parser means a parse error occurred. In that case,
24621no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24622will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24623is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24624
24625 *
24626 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24627 the model value changes. The functions are called in reverse array order, each passing the value through to the
24628 next. The last return value is used as the actual DOM value.
24629 Used to format / convert values for display in the control.
24630 * ```js
24631 * function formatter(value) {
24632 * if (value) {
24633 * return value.toUpperCase();
24634 * }
24635 * }
24636 * ngModel.$formatters.push(formatter);
24637 * ```
24638 *
24639 * @property {Object.<string, function>} $validators A collection of validators that are applied
24640 * whenever the model value changes. The key value within the object refers to the name of the
24641 * validator while the function refers to the validation operation. The validation operation is
24642 * provided with the model value as an argument and must return a true or false value depending
24643 * on the response of that validation.
24644 *
24645 * ```js
24646 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24647 * var value = modelValue || viewValue;
24648 * return /[0-9]+/.test(value) &&
24649 * /[a-z]+/.test(value) &&
24650 * /[A-Z]+/.test(value) &&
24651 * /\W+/.test(value);
24652 * };
24653 * ```
24654 *
24655 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24656 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24657 * is expected to return a promise when it is run during the model validation process. Once the promise
24658 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24659 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24660 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24661 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24662 * will only run once all synchronous validators have passed.
24663 *
24664 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24665 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24666 *
24667 * ```js
24668 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24669 * var value = modelValue || viewValue;
24670 *
24671 * // Lookup user by username
24672 * return $http.get('/api/users/' + value).
24673 * then(function resolved() {
24674 * //username exists, this means validation fails
24675 * return $q.reject('exists');
24676 * }, function rejected() {
24677 * //username does not exist, therefore this validation passes
24678 * return true;
24679 * });
24680 * };
24681 * ```
24682 *
24683 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24684 * view value has changed. It is called with no arguments, and its return value is ignored.
24685 * This can be used in place of additional $watches against the model value.
24686 *
24687 * @property {Object} $error An object hash with all failing validator ids as keys.
24688 * @property {Object} $pending An object hash with all pending validator ids as keys.
24689 *
24690 * @property {boolean} $untouched True if control has not lost focus yet.
24691 * @property {boolean} $touched True if control has lost focus.
24692 * @property {boolean} $pristine True if user has not interacted with the control yet.
24693 * @property {boolean} $dirty True if user has already interacted with the control.
24694 * @property {boolean} $valid True if there is no error.
24695 * @property {boolean} $invalid True if at least one error on the control.
24696 * @property {string} $name The name attribute of the control.
24697 *
24698 * @description
24699 *
24700 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24701 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24702 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24703 * listening to DOM events.
24704 * Such DOM related logic should be provided by other directives which make use of
24705 * `NgModelController` for data-binding to control elements.
24706 * Angular provides this DOM logic for most {@link input `input`} elements.
24707 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24708 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24709 *
24710 * @example
24711 * ### Custom Control Example
24712 * This example shows how to use `NgModelController` with a custom control to achieve
24713 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24714 * collaborate together to achieve the desired result.
24715 *
24716 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24717 * contents be edited in place by the user.
24718 *
24719 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24720 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24721 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24722 * that content using the `$sce` service.
24723 *
24724 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24725 <file name="style.css">
24726 [contenteditable] {
24727 border: 1px solid black;
24728 background-color: white;
24729 min-height: 20px;
24730 }
24731
24732 .ng-invalid {
24733 border: 1px solid red;
24734 }
24735
24736 </file>
24737 <file name="script.js">
24738 angular.module('customControl', ['ngSanitize']).
24739 directive('contenteditable', ['$sce', function($sce) {
24740 return {
24741 restrict: 'A', // only activate on element attribute
24742 require: '?ngModel', // get a hold of NgModelController
24743 link: function(scope, element, attrs, ngModel) {
24744 if (!ngModel) return; // do nothing if no ng-model
24745
24746 // Specify how UI should be updated
24747 ngModel.$render = function() {
24748 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24749 };
24750
24751 // Listen for change events to enable binding
24752 element.on('blur keyup change', function() {
24753 scope.$evalAsync(read);
24754 });
24755 read(); // initialize
24756
24757 // Write data to the model
24758 function read() {
24759 var html = element.html();
24760 // When we clear the content editable the browser leaves a <br> behind
24761 // If strip-br attribute is provided then we strip this out
24762 if ( attrs.stripBr && html == '<br>' ) {
24763 html = '';
24764 }
24765 ngModel.$setViewValue(html);
24766 }
24767 }
24768 };
24769 }]);
24770 </file>
24771 <file name="index.html">
24772 <form name="myForm">
24773 <div contenteditable
24774 name="myWidget" ng-model="userContent"
24775 strip-br="true"
24776 required>Change me!</div>
24777 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24778 <hr>
24779 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24780 </form>
24781 </file>
24782 <file name="protractor.js" type="protractor">
24783 it('should data-bind and become invalid', function() {
24784 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24785 // SafariDriver can't handle contenteditable
24786 // and Firefox driver can't clear contenteditables very well
24787 return;
24788 }
24789 var contentEditable = element(by.css('[contenteditable]'));
24790 var content = 'Change me!';
24791
24792 expect(contentEditable.getText()).toEqual(content);
24793
24794 contentEditable.clear();
24795 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24796 expect(contentEditable.getText()).toEqual('');
24797 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24798 });
24799 </file>
24800 * </example>
24801 *
24802 *
24803 */
24804var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24805 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24806 this.$viewValue = Number.NaN;
24807 this.$modelValue = Number.NaN;
24808 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24809 this.$validators = {};
24810 this.$asyncValidators = {};
24811 this.$parsers = [];
24812 this.$formatters = [];
24813 this.$viewChangeListeners = [];
24814 this.$untouched = true;
24815 this.$touched = false;
24816 this.$pristine = true;
24817 this.$dirty = false;
24818 this.$valid = true;
24819 this.$invalid = false;
24820 this.$error = {}; // keep invalid keys here
24821 this.$$success = {}; // keep valid keys here
24822 this.$pending = undefined; // keep pending keys here
24823 this.$name = $interpolate($attr.name || '', false)($scope);
24824 this.$$parentForm = nullFormCtrl;
24825
24826 var parsedNgModel = $parse($attr.ngModel),
24827 parsedNgModelAssign = parsedNgModel.assign,
24828 ngModelGet = parsedNgModel,
24829 ngModelSet = parsedNgModelAssign,
24830 pendingDebounce = null,
24831 parserValid,
24832 ctrl = this;
24833
24834 this.$$setOptions = function(options) {
24835 ctrl.$options = options;
24836 if (options && options.getterSetter) {
24837 var invokeModelGetter = $parse($attr.ngModel + '()'),
24838 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24839
24840 ngModelGet = function($scope) {
24841 var modelValue = parsedNgModel($scope);
24842 if (isFunction(modelValue)) {
24843 modelValue = invokeModelGetter($scope);
24844 }
24845 return modelValue;
24846 };
24847 ngModelSet = function($scope, newValue) {
24848 if (isFunction(parsedNgModel($scope))) {
24849 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
24850 } else {
24851 parsedNgModelAssign($scope, ctrl.$modelValue);
24852 }
24853 };
24854 } else if (!parsedNgModel.assign) {
24855 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
24856 $attr.ngModel, startingTag($element));
24857 }
24858 };
24859
24860 /**
24861 * @ngdoc method
24862 * @name ngModel.NgModelController#$render
24863 *
24864 * @description
24865 * Called when the view needs to be updated. It is expected that the user of the ng-model
24866 * directive will implement this method.
24867 *
24868 * The `$render()` method is invoked in the following situations:
24869 *
24870 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
24871 * committed value then `$render()` is called to update the input control.
24872 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
24873 * the `$viewValue` are different from last time.
24874 *
24875 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
24876 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
24877 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
24878 * invoked if you only change a property on the objects.
24879 */
24880 this.$render = noop;
24881
24882 /**
24883 * @ngdoc method
24884 * @name ngModel.NgModelController#$isEmpty
24885 *
24886 * @description
24887 * This is called when we need to determine if the value of an input is empty.
24888 *
24889 * For instance, the required directive does this to work out if the input has data or not.
24890 *
24891 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
24892 *
24893 * You can override this for input directives whose concept of being empty is different from the
24894 * default. The `checkboxInputType` directive does this because in its case a value of `false`
24895 * implies empty.
24896 *
24897 * @param {*} value The value of the input to check for emptiness.
24898 * @returns {boolean} True if `value` is "empty".
24899 */
24900 this.$isEmpty = function(value) {
24901 return isUndefined(value) || value === '' || value === null || value !== value;
24902 };
24903
24904 var currentValidationRunId = 0;
24905
24906 /**
24907 * @ngdoc method
24908 * @name ngModel.NgModelController#$setValidity
24909 *
24910 * @description
24911 * Change the validity state, and notify the form.
24912 *
24913 * This method can be called within $parsers/$formatters or a custom validation implementation.
24914 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
24915 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
24916 *
24917 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
24918 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
24919 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
24920 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
24921 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
24922 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
24923 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
24924 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
24925 * Skipped is used by Angular when validators do not run because of parse errors and
24926 * when `$asyncValidators` do not run because any of the `$validators` failed.
24927 */
24928 addSetValidityMethod({
24929 ctrl: this,
24930 $element: $element,
24931 set: function(object, property) {
24932 object[property] = true;
24933 },
24934 unset: function(object, property) {
24935 delete object[property];
24936 },
24937 $animate: $animate
24938 });
24939
24940 /**
24941 * @ngdoc method
24942 * @name ngModel.NgModelController#$setPristine
24943 *
24944 * @description
24945 * Sets the control to its pristine state.
24946 *
24947 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
24948 * state (`ng-pristine` class). A model is considered to be pristine when the control
24949 * has not been changed from when first compiled.
24950 */
24951 this.$setPristine = function() {
24952 ctrl.$dirty = false;
24953 ctrl.$pristine = true;
24954 $animate.removeClass($element, DIRTY_CLASS);
24955 $animate.addClass($element, PRISTINE_CLASS);
24956 };
24957
24958 /**
24959 * @ngdoc method
24960 * @name ngModel.NgModelController#$setDirty
24961 *
24962 * @description
24963 * Sets the control to its dirty state.
24964 *
24965 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
24966 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
24967 * from when first compiled.
24968 */
24969 this.$setDirty = function() {
24970 ctrl.$dirty = true;
24971 ctrl.$pristine = false;
24972 $animate.removeClass($element, PRISTINE_CLASS);
24973 $animate.addClass($element, DIRTY_CLASS);
24974 ctrl.$$parentForm.$setDirty();
24975 };
24976
24977 /**
24978 * @ngdoc method
24979 * @name ngModel.NgModelController#$setUntouched
24980 *
24981 * @description
24982 * Sets the control to its untouched state.
24983 *
24984 * This method can be called to remove the `ng-touched` class and set the control to its
24985 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
24986 * by default, however this function can be used to restore that state if the model has
24987 * already been touched by the user.
24988 */
24989 this.$setUntouched = function() {
24990 ctrl.$touched = false;
24991 ctrl.$untouched = true;
24992 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
24993 };
24994
24995 /**
24996 * @ngdoc method
24997 * @name ngModel.NgModelController#$setTouched
24998 *
24999 * @description
25000 * Sets the control to its touched state.
25001 *
25002 * This method can be called to remove the `ng-untouched` class and set the control to its
25003 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25004 * first focused the control element and then shifted focus away from the control (blur event).
25005 */
25006 this.$setTouched = function() {
25007 ctrl.$touched = true;
25008 ctrl.$untouched = false;
25009 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25010 };
25011
25012 /**
25013 * @ngdoc method
25014 * @name ngModel.NgModelController#$rollbackViewValue
25015 *
25016 * @description
25017 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25018 * which may be caused by a pending debounced event or because the input is waiting for a some
25019 * future event.
25020 *
25021 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25022 * as blur you can have a situation where there is a period when the `$viewValue`
25023 * is out of synch with the ngModel's `$modelValue`.
25024 *
25025 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25026 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25027 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25028 *
25029 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25030 * input which may have such events pending. This is important in order to make sure that the
25031 * input field will be updated with the new model value and any pending operations are cancelled.
25032 *
25033 * <example name="ng-model-cancel-update" module="cancel-update-example">
25034 * <file name="app.js">
25035 * angular.module('cancel-update-example', [])
25036 *
25037 * .controller('CancelUpdateController', ['$scope', function($scope) {
25038 * $scope.resetWithCancel = function(e) {
25039 * if (e.keyCode == 27) {
25040 * $scope.myForm.myInput1.$rollbackViewValue();
25041 * $scope.myValue = '';
25042 * }
25043 * };
25044 * $scope.resetWithoutCancel = function(e) {
25045 * if (e.keyCode == 27) {
25046 * $scope.myValue = '';
25047 * }
25048 * };
25049 * }]);
25050 * </file>
25051 * <file name="index.html">
25052 * <div ng-controller="CancelUpdateController">
25053 * <p>Try typing something in each input. See that the model only updates when you
25054 * blur off the input.
25055 * </p>
25056 * <p>Now see what happens if you start typing then press the Escape key</p>
25057 *
25058 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25059 * <p id="inputDescription1">With $rollbackViewValue()</p>
25060 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25061 * ng-keydown="resetWithCancel($event)"><br/>
25062 * myValue: "{{ myValue }}"
25063 *
25064 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25065 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25066 * ng-keydown="resetWithoutCancel($event)"><br/>
25067 * myValue: "{{ myValue }}"
25068 * </form>
25069 * </div>
25070 * </file>
25071 * </example>
25072 */
25073 this.$rollbackViewValue = function() {
25074 $timeout.cancel(pendingDebounce);
25075 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25076 ctrl.$render();
25077 };
25078
25079 /**
25080 * @ngdoc method
25081 * @name ngModel.NgModelController#$validate
25082 *
25083 * @description
25084 * Runs each of the registered validators (first synchronous validators and then
25085 * asynchronous validators).
25086 * If the validity changes to invalid, the model will be set to `undefined`,
25087 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25088 * If the validity changes to valid, it will set the model to the last available valid
25089 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25090 */
25091 this.$validate = function() {
25092 // ignore $validate before model is initialized
25093 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25094 return;
25095 }
25096
25097 var viewValue = ctrl.$$lastCommittedViewValue;
25098 // Note: we use the $$rawModelValue as $modelValue might have been
25099 // set to undefined during a view -> model update that found validation
25100 // errors. We can't parse the view here, since that could change
25101 // the model although neither viewValue nor the model on the scope changed
25102 var modelValue = ctrl.$$rawModelValue;
25103
25104 var prevValid = ctrl.$valid;
25105 var prevModelValue = ctrl.$modelValue;
25106
25107 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25108
25109 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25110 // If there was no change in validity, don't update the model
25111 // This prevents changing an invalid modelValue to undefined
25112 if (!allowInvalid && prevValid !== allValid) {
25113 // Note: Don't check ctrl.$valid here, as we could have
25114 // external validators (e.g. calculated on the server),
25115 // that just call $setValidity and need the model value
25116 // to calculate their validity.
25117 ctrl.$modelValue = allValid ? modelValue : undefined;
25118
25119 if (ctrl.$modelValue !== prevModelValue) {
25120 ctrl.$$writeModelToScope();
25121 }
25122 }
25123 });
25124
25125 };
25126
25127 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25128 currentValidationRunId++;
25129 var localValidationRunId = currentValidationRunId;
25130
25131 // check parser error
25132 if (!processParseErrors()) {
25133 validationDone(false);
25134 return;
25135 }
25136 if (!processSyncValidators()) {
25137 validationDone(false);
25138 return;
25139 }
25140 processAsyncValidators();
25141
25142 function processParseErrors() {
25143 var errorKey = ctrl.$$parserName || 'parse';
25144 if (isUndefined(parserValid)) {
25145 setValidity(errorKey, null);
25146 } else {
25147 if (!parserValid) {
25148 forEach(ctrl.$validators, function(v, name) {
25149 setValidity(name, null);
25150 });
25151 forEach(ctrl.$asyncValidators, function(v, name) {
25152 setValidity(name, null);
25153 });
25154 }
25155 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25156 setValidity(errorKey, parserValid);
25157 return parserValid;
25158 }
25159 return true;
25160 }
25161
25162 function processSyncValidators() {
25163 var syncValidatorsValid = true;
25164 forEach(ctrl.$validators, function(validator, name) {
25165 var result = validator(modelValue, viewValue);
25166 syncValidatorsValid = syncValidatorsValid && result;
25167 setValidity(name, result);
25168 });
25169 if (!syncValidatorsValid) {
25170 forEach(ctrl.$asyncValidators, function(v, name) {
25171 setValidity(name, null);
25172 });
25173 return false;
25174 }
25175 return true;
25176 }
25177
25178 function processAsyncValidators() {
25179 var validatorPromises = [];
25180 var allValid = true;
25181 forEach(ctrl.$asyncValidators, function(validator, name) {
25182 var promise = validator(modelValue, viewValue);
25183 if (!isPromiseLike(promise)) {
25184 throw ngModelMinErr("$asyncValidators",
25185 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25186 }
25187 setValidity(name, undefined);
25188 validatorPromises.push(promise.then(function() {
25189 setValidity(name, true);
25190 }, function(error) {
25191 allValid = false;
25192 setValidity(name, false);
25193 }));
25194 });
25195 if (!validatorPromises.length) {
25196 validationDone(true);
25197 } else {
25198 $q.all(validatorPromises).then(function() {
25199 validationDone(allValid);
25200 }, noop);
25201 }
25202 }
25203
25204 function setValidity(name, isValid) {
25205 if (localValidationRunId === currentValidationRunId) {
25206 ctrl.$setValidity(name, isValid);
25207 }
25208 }
25209
25210 function validationDone(allValid) {
25211 if (localValidationRunId === currentValidationRunId) {
25212
25213 doneCallback(allValid);
25214 }
25215 }
25216 };
25217
25218 /**
25219 * @ngdoc method
25220 * @name ngModel.NgModelController#$commitViewValue
25221 *
25222 * @description
25223 * Commit a pending update to the `$modelValue`.
25224 *
25225 * Updates may be pending by a debounced event or because the input is waiting for a some future
25226 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25227 * usually handles calling this in response to input events.
25228 */
25229 this.$commitViewValue = function() {
25230 var viewValue = ctrl.$viewValue;
25231
25232 $timeout.cancel(pendingDebounce);
25233
25234 // If the view value has not changed then we should just exit, except in the case where there is
25235 // a native validator on the element. In this case the validation state may have changed even though
25236 // the viewValue has stayed empty.
25237 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25238 return;
25239 }
25240 ctrl.$$lastCommittedViewValue = viewValue;
25241
25242 // change to dirty
25243 if (ctrl.$pristine) {
25244 this.$setDirty();
25245 }
25246 this.$$parseAndValidate();
25247 };
25248
25249 this.$$parseAndValidate = function() {
25250 var viewValue = ctrl.$$lastCommittedViewValue;
25251 var modelValue = viewValue;
25252 parserValid = isUndefined(modelValue) ? undefined : true;
25253
25254 if (parserValid) {
25255 for (var i = 0; i < ctrl.$parsers.length; i++) {
25256 modelValue = ctrl.$parsers[i](modelValue);
25257 if (isUndefined(modelValue)) {
25258 parserValid = false;
25259 break;
25260 }
25261 }
25262 }
25263 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25264 // ctrl.$modelValue has not been touched yet...
25265 ctrl.$modelValue = ngModelGet($scope);
25266 }
25267 var prevModelValue = ctrl.$modelValue;
25268 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25269 ctrl.$$rawModelValue = modelValue;
25270
25271 if (allowInvalid) {
25272 ctrl.$modelValue = modelValue;
25273 writeToModelIfNeeded();
25274 }
25275
25276 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25277 // This can happen if e.g. $setViewValue is called from inside a parser
25278 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25279 if (!allowInvalid) {
25280 // Note: Don't check ctrl.$valid here, as we could have
25281 // external validators (e.g. calculated on the server),
25282 // that just call $setValidity and need the model value
25283 // to calculate their validity.
25284 ctrl.$modelValue = allValid ? modelValue : undefined;
25285 writeToModelIfNeeded();
25286 }
25287 });
25288
25289 function writeToModelIfNeeded() {
25290 if (ctrl.$modelValue !== prevModelValue) {
25291 ctrl.$$writeModelToScope();
25292 }
25293 }
25294 };
25295
25296 this.$$writeModelToScope = function() {
25297 ngModelSet($scope, ctrl.$modelValue);
25298 forEach(ctrl.$viewChangeListeners, function(listener) {
25299 try {
25300 listener();
25301 } catch (e) {
25302 $exceptionHandler(e);
25303 }
25304 });
25305 };
25306
25307 /**
25308 * @ngdoc method
25309 * @name ngModel.NgModelController#$setViewValue
25310 *
25311 * @description
25312 * Update the view value.
25313 *
25314 * This method should be called when a control wants to change the view value; typically,
25315 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25316 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25317 * calls it when an option is selected.
25318 *
25319 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25320 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25321 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25322 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25323 * in the `$viewChangeListeners` list, are called.
25324 *
25325 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25326 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25327 * `updateOn` events is triggered on the DOM element.
25328 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25329 * directive is used with a custom debounce for this particular event.
25330 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25331 * is specified, once the timer runs out.
25332 *
25333 * When used with standard inputs, the view value will always be a string (which is in some cases
25334 * parsed into another type, such as a `Date` object for `input[date]`.)
25335 * However, custom controls might also pass objects to this method. In this case, we should make
25336 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25337 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25338 * the property of the object then ngModel will not realise that the object has changed and
25339 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25340 * not change properties of the copy once it has been passed to `$setViewValue`.
25341 * Otherwise you may cause the model value on the scope to change incorrectly.
25342 *
25343 * <div class="alert alert-info">
25344 * In any case, the value passed to the method should always reflect the current value
25345 * of the control. For example, if you are calling `$setViewValue` for an input element,
25346 * you should pass the input DOM value. Otherwise, the control and the scope model become
25347 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25348 * the control's DOM value in any way. If we want to change the control's DOM value
25349 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25350 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25351 * to update the DOM, and finally call `$validate` on it.
25352 * </div>
25353 *
25354 * @param {*} value value from the view.
25355 * @param {string} trigger Event that triggered the update.
25356 */
25357 this.$setViewValue = function(value, trigger) {
25358 ctrl.$viewValue = value;
25359 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25360 ctrl.$$debounceViewValueCommit(trigger);
25361 }
25362 };
25363
25364 this.$$debounceViewValueCommit = function(trigger) {
25365 var debounceDelay = 0,
25366 options = ctrl.$options,
25367 debounce;
25368
25369 if (options && isDefined(options.debounce)) {
25370 debounce = options.debounce;
25371 if (isNumber(debounce)) {
25372 debounceDelay = debounce;
25373 } else if (isNumber(debounce[trigger])) {
25374 debounceDelay = debounce[trigger];
25375 } else if (isNumber(debounce['default'])) {
25376 debounceDelay = debounce['default'];
25377 }
25378 }
25379
25380 $timeout.cancel(pendingDebounce);
25381 if (debounceDelay) {
25382 pendingDebounce = $timeout(function() {
25383 ctrl.$commitViewValue();
25384 }, debounceDelay);
25385 } else if ($rootScope.$$phase) {
25386 ctrl.$commitViewValue();
25387 } else {
25388 $scope.$apply(function() {
25389 ctrl.$commitViewValue();
25390 });
25391 }
25392 };
25393
25394 // model -> value
25395 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25396 // 1. scope value is 'a'
25397 // 2. user enters 'b'
25398 // 3. ng-change kicks in and reverts scope value to 'a'
25399 // -> scope value did not change since the last digest as
25400 // ng-change executes in apply phase
25401 // 4. view should be changed back to 'a'
25402 $scope.$watch(function ngModelWatch() {
25403 var modelValue = ngModelGet($scope);
25404
25405 // if scope model value and ngModel value are out of sync
25406 // TODO(perf): why not move this to the action fn?
25407 if (modelValue !== ctrl.$modelValue &&
25408 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25409 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25410 ) {
25411 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25412 parserValid = undefined;
25413
25414 var formatters = ctrl.$formatters,
25415 idx = formatters.length;
25416
25417 var viewValue = modelValue;
25418 while (idx--) {
25419 viewValue = formatters[idx](viewValue);
25420 }
25421 if (ctrl.$viewValue !== viewValue) {
25422 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25423 ctrl.$render();
25424
25425 ctrl.$$runValidators(modelValue, viewValue, noop);
25426 }
25427 }
25428
25429 return modelValue;
25430 });
25431}];
25432
25433
25434/**
25435 * @ngdoc directive
25436 * @name ngModel
25437 *
25438 * @element input
25439 * @priority 1
25440 *
25441 * @description
25442 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25443 * property on the scope using {@link ngModel.NgModelController NgModelController},
25444 * which is created and exposed by this directive.
25445 *
25446 * `ngModel` is responsible for:
25447 *
25448 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25449 * require.
25450 * - Providing validation behavior (i.e. required, number, email, url).
25451 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25452 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25453 * - Registering the control with its parent {@link ng.directive:form form}.
25454 *
25455 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25456 * current scope. If the property doesn't already exist on this scope, it will be created
25457 * implicitly and added to the scope.
25458 *
25459 * For best practices on using `ngModel`, see:
25460 *
25461 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25462 *
25463 * For basic examples, how to use `ngModel`, see:
25464 *
25465 * - {@link ng.directive:input input}
25466 * - {@link input[text] text}
25467 * - {@link input[checkbox] checkbox}
25468 * - {@link input[radio] radio}
25469 * - {@link input[number] number}
25470 * - {@link input[email] email}
25471 * - {@link input[url] url}
25472 * - {@link input[date] date}
25473 * - {@link input[datetime-local] datetime-local}
25474 * - {@link input[time] time}
25475 * - {@link input[month] month}
25476 * - {@link input[week] week}
25477 * - {@link ng.directive:select select}
25478 * - {@link ng.directive:textarea textarea}
25479 *
25480 * # CSS classes
25481 * The following CSS classes are added and removed on the associated input/select/textarea element
25482 * depending on the validity of the model.
25483 *
25484 * - `ng-valid`: the model is valid
25485 * - `ng-invalid`: the model is invalid
25486 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25487 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25488 * - `ng-pristine`: the control hasn't been interacted with yet
25489 * - `ng-dirty`: the control has been interacted with
25490 * - `ng-touched`: the control has been blurred
25491 * - `ng-untouched`: the control hasn't been blurred
25492 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25493 *
25494 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25495 *
25496 * ## Animation Hooks
25497 *
25498 * Animations within models are triggered when any of the associated CSS classes are added and removed
25499 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25500 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25501 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25502 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25503 *
25504 * The following example shows a simple way to utilize CSS transitions to style an input element
25505 * that has been rendered as invalid after it has been validated:
25506 *
25507 * <pre>
25508 * //be sure to include ngAnimate as a module to hook into more
25509 * //advanced animations
25510 * .my-input {
25511 * transition:0.5s linear all;
25512 * background: white;
25513 * }
25514 * .my-input.ng-invalid {
25515 * background: red;
25516 * color:white;
25517 * }
25518 * </pre>
25519 *
25520 * @example
25521 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25522 <file name="index.html">
25523 <script>
25524 angular.module('inputExample', [])
25525 .controller('ExampleController', ['$scope', function($scope) {
25526 $scope.val = '1';
25527 }]);
25528 </script>
25529 <style>
25530 .my-input {
25531 transition:all linear 0.5s;
25532 background: transparent;
25533 }
25534 .my-input.ng-invalid {
25535 color:white;
25536 background: red;
25537 }
25538 </style>
25539 <p id="inputDescription">
25540 Update input to see transitions when valid/invalid.
25541 Integer is a valid value.
25542 </p>
25543 <form name="testForm" ng-controller="ExampleController">
25544 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25545 aria-describedby="inputDescription" />
25546 </form>
25547 </file>
25548 * </example>
25549 *
25550 * ## Binding to a getter/setter
25551 *
25552 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25553 * function that returns a representation of the model when called with zero arguments, and sets
25554 * the internal state of a model when called with an argument. It's sometimes useful to use this
25555 * for models that have an internal representation that's different from what the model exposes
25556 * to the view.
25557 *
25558 * <div class="alert alert-success">
25559 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25560 * frequently than other parts of your code.
25561 * </div>
25562 *
25563 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25564 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25565 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25566 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25567 *
25568 * The following example shows how to use `ngModel` with a getter/setter:
25569 *
25570 * @example
25571 * <example name="ngModel-getter-setter" module="getterSetterExample">
25572 <file name="index.html">
25573 <div ng-controller="ExampleController">
25574 <form name="userForm">
25575 <label>Name:
25576 <input type="text" name="userName"
25577 ng-model="user.name"
25578 ng-model-options="{ getterSetter: true }" />
25579 </label>
25580 </form>
25581 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25582 </div>
25583 </file>
25584 <file name="app.js">
25585 angular.module('getterSetterExample', [])
25586 .controller('ExampleController', ['$scope', function($scope) {
25587 var _name = 'Brian';
25588 $scope.user = {
25589 name: function(newName) {
25590 // Note that newName can be undefined for two reasons:
25591 // 1. Because it is called as a getter and thus called with no arguments
25592 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25593 // input is invalid
25594 return arguments.length ? (_name = newName) : _name;
25595 }
25596 };
25597 }]);
25598 </file>
25599 * </example>
25600 */
25601var ngModelDirective = ['$rootScope', function($rootScope) {
25602 return {
25603 restrict: 'A',
25604 require: ['ngModel', '^?form', '^?ngModelOptions'],
25605 controller: NgModelController,
25606 // Prelink needs to run before any input directive
25607 // so that we can set the NgModelOptions in NgModelController
25608 // before anyone else uses it.
25609 priority: 1,
25610 compile: function ngModelCompile(element) {
25611 // Setup initial state of the control
25612 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25613
25614 return {
25615 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25616 var modelCtrl = ctrls[0],
25617 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25618
25619 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25620
25621 // notify others, especially parent forms
25622 formCtrl.$addControl(modelCtrl);
25623
25624 attr.$observe('name', function(newValue) {
25625 if (modelCtrl.$name !== newValue) {
25626 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25627 }
25628 });
25629
25630 scope.$on('$destroy', function() {
25631 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25632 });
25633 },
25634 post: function ngModelPostLink(scope, element, attr, ctrls) {
25635 var modelCtrl = ctrls[0];
25636 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25637 element.on(modelCtrl.$options.updateOn, function(ev) {
25638 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25639 });
25640 }
25641
25642 element.on('blur', function(ev) {
25643 if (modelCtrl.$touched) return;
25644
25645 if ($rootScope.$$phase) {
25646 scope.$evalAsync(modelCtrl.$setTouched);
25647 } else {
25648 scope.$apply(modelCtrl.$setTouched);
25649 }
25650 });
25651 }
25652 };
25653 }
25654 };
25655}];
25656
25657var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25658
25659/**
25660 * @ngdoc directive
25661 * @name ngModelOptions
25662 *
25663 * @description
25664 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25665 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25666 * takes place when a timer expires; this timer will be reset after another change takes place.
25667 *
25668 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25669 * be different from the value in the actual model. This means that if you update the model you
25670 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25671 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25672 *
25673 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25674 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25675 * important because `form` controllers are published to the related scope under the name in their
25676 * `name` attribute.
25677 *
25678 * Any pending changes will take place immediately when an enclosing form is submitted via the
25679 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25680 * to have access to the updated model.
25681 *
25682 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25683 *
25684 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25685 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25686 * events using an space delimited list. There is a special event called `default` that
25687 * matches the default events belonging of the control.
25688 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25689 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25690 * custom value for each event. For example:
25691 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25692 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25693 * not validate correctly instead of the default behavior of setting the model to undefined.
25694 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25695 `ngModel` as getters/setters.
25696 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25697 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25698 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25699 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25700 * If not specified, the timezone of the browser will be used.
25701 *
25702 * @example
25703
25704 The following example shows how to override immediate updates. Changes on the inputs within the
25705 form will update the model only when the control loses focus (blur event). If `escape` key is
25706 pressed while the input field is focused, the value is reset to the value in the current model.
25707
25708 <example name="ngModelOptions-directive-blur" module="optionsExample">
25709 <file name="index.html">
25710 <div ng-controller="ExampleController">
25711 <form name="userForm">
25712 <label>Name:
25713 <input type="text" name="userName"
25714 ng-model="user.name"
25715 ng-model-options="{ updateOn: 'blur' }"
25716 ng-keyup="cancel($event)" />
25717 </label><br />
25718 <label>Other data:
25719 <input type="text" ng-model="user.data" />
25720 </label><br />
25721 </form>
25722 <pre>user.name = <span ng-bind="user.name"></span></pre>
25723 </div>
25724 </file>
25725 <file name="app.js">
25726 angular.module('optionsExample', [])
25727 .controller('ExampleController', ['$scope', function($scope) {
25728 $scope.user = { name: 'say', data: '' };
25729
25730 $scope.cancel = function(e) {
25731 if (e.keyCode == 27) {
25732 $scope.userForm.userName.$rollbackViewValue();
25733 }
25734 };
25735 }]);
25736 </file>
25737 <file name="protractor.js" type="protractor">
25738 var model = element(by.binding('user.name'));
25739 var input = element(by.model('user.name'));
25740 var other = element(by.model('user.data'));
25741
25742 it('should allow custom events', function() {
25743 input.sendKeys(' hello');
25744 input.click();
25745 expect(model.getText()).toEqual('say');
25746 other.click();
25747 expect(model.getText()).toEqual('say hello');
25748 });
25749
25750 it('should $rollbackViewValue when model changes', function() {
25751 input.sendKeys(' hello');
25752 expect(input.getAttribute('value')).toEqual('say hello');
25753 input.sendKeys(protractor.Key.ESCAPE);
25754 expect(input.getAttribute('value')).toEqual('say');
25755 other.click();
25756 expect(model.getText()).toEqual('say');
25757 });
25758 </file>
25759 </example>
25760
25761 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25762 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25763
25764 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25765 <file name="index.html">
25766 <div ng-controller="ExampleController">
25767 <form name="userForm">
25768 <label>Name:
25769 <input type="text" name="userName"
25770 ng-model="user.name"
25771 ng-model-options="{ debounce: 1000 }" />
25772 </label>
25773 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25774 <br />
25775 </form>
25776 <pre>user.name = <span ng-bind="user.name"></span></pre>
25777 </div>
25778 </file>
25779 <file name="app.js">
25780 angular.module('optionsExample', [])
25781 .controller('ExampleController', ['$scope', function($scope) {
25782 $scope.user = { name: 'say' };
25783 }]);
25784 </file>
25785 </example>
25786
25787 This one shows how to bind to getter/setters:
25788
25789 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25790 <file name="index.html">
25791 <div ng-controller="ExampleController">
25792 <form name="userForm">
25793 <label>Name:
25794 <input type="text" name="userName"
25795 ng-model="user.name"
25796 ng-model-options="{ getterSetter: true }" />
25797 </label>
25798 </form>
25799 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25800 </div>
25801 </file>
25802 <file name="app.js">
25803 angular.module('getterSetterExample', [])
25804 .controller('ExampleController', ['$scope', function($scope) {
25805 var _name = 'Brian';
25806 $scope.user = {
25807 name: function(newName) {
25808 // Note that newName can be undefined for two reasons:
25809 // 1. Because it is called as a getter and thus called with no arguments
25810 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25811 // input is invalid
25812 return arguments.length ? (_name = newName) : _name;
25813 }
25814 };
25815 }]);
25816 </file>
25817 </example>
25818 */
25819var ngModelOptionsDirective = function() {
25820 return {
25821 restrict: 'A',
25822 controller: ['$scope', '$attrs', function($scope, $attrs) {
25823 var that = this;
25824 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25825 // Allow adding/overriding bound events
25826 if (isDefined(this.$options.updateOn)) {
25827 this.$options.updateOnDefault = false;
25828 // extract "default" pseudo-event from list of events that can trigger a model update
25829 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25830 that.$options.updateOnDefault = true;
25831 return ' ';
25832 }));
25833 } else {
25834 this.$options.updateOnDefault = true;
25835 }
25836 }]
25837 };
25838};
25839
25840
25841
25842// helper methods
25843function addSetValidityMethod(context) {
25844 var ctrl = context.ctrl,
25845 $element = context.$element,
25846 classCache = {},
25847 set = context.set,
25848 unset = context.unset,
25849 $animate = context.$animate;
25850
25851 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
25852
25853 ctrl.$setValidity = setValidity;
25854
25855 function setValidity(validationErrorKey, state, controller) {
25856 if (isUndefined(state)) {
25857 createAndSet('$pending', validationErrorKey, controller);
25858 } else {
25859 unsetAndCleanup('$pending', validationErrorKey, controller);
25860 }
25861 if (!isBoolean(state)) {
25862 unset(ctrl.$error, validationErrorKey, controller);
25863 unset(ctrl.$$success, validationErrorKey, controller);
25864 } else {
25865 if (state) {
25866 unset(ctrl.$error, validationErrorKey, controller);
25867 set(ctrl.$$success, validationErrorKey, controller);
25868 } else {
25869 set(ctrl.$error, validationErrorKey, controller);
25870 unset(ctrl.$$success, validationErrorKey, controller);
25871 }
25872 }
25873 if (ctrl.$pending) {
25874 cachedToggleClass(PENDING_CLASS, true);
25875 ctrl.$valid = ctrl.$invalid = undefined;
25876 toggleValidationCss('', null);
25877 } else {
25878 cachedToggleClass(PENDING_CLASS, false);
25879 ctrl.$valid = isObjectEmpty(ctrl.$error);
25880 ctrl.$invalid = !ctrl.$valid;
25881 toggleValidationCss('', ctrl.$valid);
25882 }
25883
25884 // re-read the state as the set/unset methods could have
25885 // combined state in ctrl.$error[validationError] (used for forms),
25886 // where setting/unsetting only increments/decrements the value,
25887 // and does not replace it.
25888 var combinedState;
25889 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
25890 combinedState = undefined;
25891 } else if (ctrl.$error[validationErrorKey]) {
25892 combinedState = false;
25893 } else if (ctrl.$$success[validationErrorKey]) {
25894 combinedState = true;
25895 } else {
25896 combinedState = null;
25897 }
25898
25899 toggleValidationCss(validationErrorKey, combinedState);
25900 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
25901 }
25902
25903 function createAndSet(name, value, controller) {
25904 if (!ctrl[name]) {
25905 ctrl[name] = {};
25906 }
25907 set(ctrl[name], value, controller);
25908 }
25909
25910 function unsetAndCleanup(name, value, controller) {
25911 if (ctrl[name]) {
25912 unset(ctrl[name], value, controller);
25913 }
25914 if (isObjectEmpty(ctrl[name])) {
25915 ctrl[name] = undefined;
25916 }
25917 }
25918
25919 function cachedToggleClass(className, switchValue) {
25920 if (switchValue && !classCache[className]) {
25921 $animate.addClass($element, className);
25922 classCache[className] = true;
25923 } else if (!switchValue && classCache[className]) {
25924 $animate.removeClass($element, className);
25925 classCache[className] = false;
25926 }
25927 }
25928
25929 function toggleValidationCss(validationErrorKey, isValid) {
25930 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
25931
25932 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
25933 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
25934 }
25935}
25936
25937function isObjectEmpty(obj) {
25938 if (obj) {
25939 for (var prop in obj) {
25940 if (obj.hasOwnProperty(prop)) {
25941 return false;
25942 }
25943 }
25944 }
25945 return true;
25946}
25947
25948/**
25949 * @ngdoc directive
25950 * @name ngNonBindable
25951 * @restrict AC
25952 * @priority 1000
25953 *
25954 * @description
25955 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
25956 * DOM element. This is useful if the element contains what appears to be Angular directives and
25957 * bindings but which should be ignored by Angular. This could be the case if you have a site that
25958 * displays snippets of code, for instance.
25959 *
25960 * @element ANY
25961 *
25962 * @example
25963 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
25964 * but the one wrapped in `ngNonBindable` is left alone.
25965 *
25966 * @example
25967 <example>
25968 <file name="index.html">
25969 <div>Normal: {{1 + 2}}</div>
25970 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
25971 </file>
25972 <file name="protractor.js" type="protractor">
25973 it('should check ng-non-bindable', function() {
25974 expect(element(by.binding('1 + 2')).getText()).toContain('3');
25975 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
25976 });
25977 </file>
25978 </example>
25979 */
25980var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
25981
25982/* global jqLiteRemove */
25983
25984var ngOptionsMinErr = minErr('ngOptions');
25985
25986/**
25987 * @ngdoc directive
25988 * @name ngOptions
25989 * @restrict A
25990 *
25991 * @description
25992 *
25993 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
25994 * elements for the `<select>` element using the array or object obtained by evaluating the
25995 * `ngOptions` comprehension expression.
25996 *
25997 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
25998 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
25999 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26000 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26001 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26002 * to a non-string value. This is because an option element can only be bound to string values at
26003 * present.
26004 *
26005 * When an item in the `<select>` menu is selected, the array element or object property
26006 * represented by the selected option will be bound to the model identified by the `ngModel`
26007 * directive.
26008 *
26009 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26010 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26011 * option. See example below for demonstration.
26012 *
26013 * ## Complex Models (objects or collections)
26014 *
26015 * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when
26016 * binding any input directive to a model that is an object or a collection.
26017 *
26018 * Since this is a common situation for `ngOptions` the directive additionally watches the model using
26019 * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in
26020 * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual
26021 * object/collection has not changed identity but only a property on the object or an item in the collection
26022 * changes.
26023 *
26024 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26025 * if the model is an array). This means that changing a property deeper inside the object/collection that the
26026 * first level will not trigger a re-rendering.
26027 *
26028 *
26029 * ## `select` **`as`**
26030 *
26031 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26032 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26033 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26034 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26035 *
26036 *
26037 * ### `select` **`as`** and **`track by`**
26038 *
26039 * <div class="alert alert-warning">
26040 * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
26041 * </div>
26042 *
26043 * Consider the following example:
26044 *
26045 * ```html
26046 * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select>
26047 * ```
26048 *
26049 * ```js
26050 * $scope.values = [{
26051 * id: 1,
26052 * label: 'aLabel',
26053 * subItem: { name: 'aSubItem' }
26054 * }, {
26055 * id: 2,
26056 * label: 'bLabel',
26057 * subItem: { name: 'bSubItem' }
26058 * }];
26059 *
26060 * $scope.selected = { name: 'aSubItem' };
26061 * ```
26062 *
26063 * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
26064 * of the data source (to `item` in this example). To calculate whether an element is selected, we do the
26065 * following:
26066 *
26067 * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
26068 * 2. Apply **`track by`** to the already selected value in `ngModel`.
26069 * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
26070 * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
26071 * a wrong object, the selected element can't be found, `<select>` is always reset to the "not
26072 * selected" option.
26073 *
26074 *
26075 * @param {string} ngModel Assignable angular expression to data-bind to.
26076 * @param {string=} name Property name of the form under which the control is published.
26077 * @param {string=} required The control is considered valid only if value is entered.
26078 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26079 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26080 * `required` when you want to data-bind to the `required` attribute.
26081 * @param {comprehension_expression=} ngOptions in one of the following forms:
26082 *
26083 * * for array data sources:
26084 * * `label` **`for`** `value` **`in`** `array`
26085 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26086 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26087 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26088 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26089 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26090 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26091 * (for including a filter with `track by`)
26092 * * for object data sources:
26093 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26094 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26095 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26096 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26097 * * `select` **`as`** `label` **`group by`** `group`
26098 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26099 * * `select` **`as`** `label` **`disable when`** `disable`
26100 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26101 *
26102 * Where:
26103 *
26104 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26105 * * `value`: local variable which will refer to each item in the `array` or each property value
26106 * of `object` during iteration.
26107 * * `key`: local variable which will refer to a property name in `object` during iteration.
26108 * * `label`: The result of this expression will be the label for `<option>` element. The
26109 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26110 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26111 * element. If not specified, `select` expression will default to `value`.
26112 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26113 * DOM element.
26114 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26115 * element. Return `true` to disable.
26116 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26117 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26118 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26119 * even when the options are recreated (e.g. reloaded from the server).
26120 *
26121 * @example
26122 <example module="selectExample">
26123 <file name="index.html">
26124 <script>
26125 angular.module('selectExample', [])
26126 .controller('ExampleController', ['$scope', function($scope) {
26127 $scope.colors = [
26128 {name:'black', shade:'dark'},
26129 {name:'white', shade:'light', notAnOption: true},
26130 {name:'red', shade:'dark'},
26131 {name:'blue', shade:'dark', notAnOption: true},
26132 {name:'yellow', shade:'light', notAnOption: false}
26133 ];
26134 $scope.myColor = $scope.colors[2]; // red
26135 }]);
26136 </script>
26137 <div ng-controller="ExampleController">
26138 <ul>
26139 <li ng-repeat="color in colors">
26140 <label>Name: <input ng-model="color.name"></label>
26141 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26142 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26143 </li>
26144 <li>
26145 <button ng-click="colors.push({})">add</button>
26146 </li>
26147 </ul>
26148 <hr/>
26149 <label>Color (null not allowed):
26150 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26151 </label><br/>
26152 <label>Color (null allowed):
26153 <span class="nullable">
26154 <select ng-model="myColor" ng-options="color.name for color in colors">
26155 <option value="">-- choose color --</option>
26156 </select>
26157 </span></label><br/>
26158
26159 <label>Color grouped by shade:
26160 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26161 </select>
26162 </label><br/>
26163
26164 <label>Color grouped by shade, with some disabled:
26165 <select ng-model="myColor"
26166 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26167 </select>
26168 </label><br/>
26169
26170
26171
26172 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26173 <br/>
26174 <hr/>
26175 Currently selected: {{ {selected_color:myColor} }}
26176 <div style="border:solid 1px black; height:20px"
26177 ng-style="{'background-color':myColor.name}">
26178 </div>
26179 </div>
26180 </file>
26181 <file name="protractor.js" type="protractor">
26182 it('should check ng-options', function() {
26183 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26184 element.all(by.model('myColor')).first().click();
26185 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26186 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26187 element(by.css('.nullable select[ng-model="myColor"]')).click();
26188 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26189 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26190 });
26191 </file>
26192 </example>
26193 */
26194
26195// jshint maxlen: false
26196// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26197var 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]+?))?$/;
26198 // 1: value expression (valueFn)
26199 // 2: label expression (displayFn)
26200 // 3: group by expression (groupByFn)
26201 // 4: disable when expression (disableWhenFn)
26202 // 5: array item variable name
26203 // 6: object item key variable name
26204 // 7: object item value variable name
26205 // 8: collection expression
26206 // 9: track by expression
26207// jshint maxlen: 100
26208
26209
26210var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26211
26212 function parseOptionsExpression(optionsExp, selectElement, scope) {
26213
26214 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26215 if (!(match)) {
26216 throw ngOptionsMinErr('iexp',
26217 "Expected expression in form of " +
26218 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26219 " but got '{0}'. Element: {1}",
26220 optionsExp, startingTag(selectElement));
26221 }
26222
26223 // Extract the parts from the ngOptions expression
26224
26225 // The variable name for the value of the item in the collection
26226 var valueName = match[5] || match[7];
26227 // The variable name for the key of the item in the collection
26228 var keyName = match[6];
26229
26230 // An expression that generates the viewValue for an option if there is a label expression
26231 var selectAs = / as /.test(match[0]) && match[1];
26232 // An expression that is used to track the id of each object in the options collection
26233 var trackBy = match[9];
26234 // An expression that generates the viewValue for an option if there is no label expression
26235 var valueFn = $parse(match[2] ? match[1] : valueName);
26236 var selectAsFn = selectAs && $parse(selectAs);
26237 var viewValueFn = selectAsFn || valueFn;
26238 var trackByFn = trackBy && $parse(trackBy);
26239
26240 // Get the value by which we are going to track the option
26241 // if we have a trackFn then use that (passing scope and locals)
26242 // otherwise just hash the given viewValue
26243 var getTrackByValueFn = trackBy ?
26244 function(value, locals) { return trackByFn(scope, locals); } :
26245 function getHashOfValue(value) { return hashKey(value); };
26246 var getTrackByValue = function(value, key) {
26247 return getTrackByValueFn(value, getLocals(value, key));
26248 };
26249
26250 var displayFn = $parse(match[2] || match[1]);
26251 var groupByFn = $parse(match[3] || '');
26252 var disableWhenFn = $parse(match[4] || '');
26253 var valuesFn = $parse(match[8]);
26254
26255 var locals = {};
26256 var getLocals = keyName ? function(value, key) {
26257 locals[keyName] = key;
26258 locals[valueName] = value;
26259 return locals;
26260 } : function(value) {
26261 locals[valueName] = value;
26262 return locals;
26263 };
26264
26265
26266 function Option(selectValue, viewValue, label, group, disabled) {
26267 this.selectValue = selectValue;
26268 this.viewValue = viewValue;
26269 this.label = label;
26270 this.group = group;
26271 this.disabled = disabled;
26272 }
26273
26274 function getOptionValuesKeys(optionValues) {
26275 var optionValuesKeys;
26276
26277 if (!keyName && isArrayLike(optionValues)) {
26278 optionValuesKeys = optionValues;
26279 } else {
26280 // if object, extract keys, in enumeration order, unsorted
26281 optionValuesKeys = [];
26282 for (var itemKey in optionValues) {
26283 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26284 optionValuesKeys.push(itemKey);
26285 }
26286 }
26287 }
26288 return optionValuesKeys;
26289 }
26290
26291 return {
26292 trackBy: trackBy,
26293 getTrackByValue: getTrackByValue,
26294 getWatchables: $parse(valuesFn, function(optionValues) {
26295 // Create a collection of things that we would like to watch (watchedArray)
26296 // so that they can all be watched using a single $watchCollection
26297 // that only runs the handler once if anything changes
26298 var watchedArray = [];
26299 optionValues = optionValues || [];
26300
26301 var optionValuesKeys = getOptionValuesKeys(optionValues);
26302 var optionValuesLength = optionValuesKeys.length;
26303 for (var index = 0; index < optionValuesLength; index++) {
26304 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26305 var value = optionValues[key];
26306
26307 var locals = getLocals(optionValues[key], key);
26308 var selectValue = getTrackByValueFn(optionValues[key], locals);
26309 watchedArray.push(selectValue);
26310
26311 // Only need to watch the displayFn if there is a specific label expression
26312 if (match[2] || match[1]) {
26313 var label = displayFn(scope, locals);
26314 watchedArray.push(label);
26315 }
26316
26317 // Only need to watch the disableWhenFn if there is a specific disable expression
26318 if (match[4]) {
26319 var disableWhen = disableWhenFn(scope, locals);
26320 watchedArray.push(disableWhen);
26321 }
26322 }
26323 return watchedArray;
26324 }),
26325
26326 getOptions: function() {
26327
26328 var optionItems = [];
26329 var selectValueMap = {};
26330
26331 // The option values were already computed in the `getWatchables` fn,
26332 // which must have been called to trigger `getOptions`
26333 var optionValues = valuesFn(scope) || [];
26334 var optionValuesKeys = getOptionValuesKeys(optionValues);
26335 var optionValuesLength = optionValuesKeys.length;
26336
26337 for (var index = 0; index < optionValuesLength; index++) {
26338 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26339 var value = optionValues[key];
26340 var locals = getLocals(value, key);
26341 var viewValue = viewValueFn(scope, locals);
26342 var selectValue = getTrackByValueFn(viewValue, locals);
26343 var label = displayFn(scope, locals);
26344 var group = groupByFn(scope, locals);
26345 var disabled = disableWhenFn(scope, locals);
26346 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26347
26348 optionItems.push(optionItem);
26349 selectValueMap[selectValue] = optionItem;
26350 }
26351
26352 return {
26353 items: optionItems,
26354 selectValueMap: selectValueMap,
26355 getOptionFromViewValue: function(value) {
26356 return selectValueMap[getTrackByValue(value)];
26357 },
26358 getViewValueFromOption: function(option) {
26359 // If the viewValue could be an object that may be mutated by the application,
26360 // we need to make a copy and not return the reference to the value on the option.
26361 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26362 }
26363 };
26364 }
26365 };
26366 }
26367
26368
26369 // we can't just jqLite('<option>') since jqLite is not smart enough
26370 // to create it in <select> and IE barfs otherwise.
26371 var optionTemplate = document.createElement('option'),
26372 optGroupTemplate = document.createElement('optgroup');
26373
26374 return {
26375 restrict: 'A',
26376 terminal: true,
26377 require: ['select', '?ngModel'],
26378 link: function(scope, selectElement, attr, ctrls) {
26379
26380 // if ngModel is not defined, we don't need to do anything
26381 var ngModelCtrl = ctrls[1];
26382 if (!ngModelCtrl) return;
26383
26384 var selectCtrl = ctrls[0];
26385 var multiple = attr.multiple;
26386
26387 // The emptyOption allows the application developer to provide their own custom "empty"
26388 // option when the viewValue does not match any of the option values.
26389 var emptyOption;
26390 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26391 if (children[i].value === '') {
26392 emptyOption = children.eq(i);
26393 break;
26394 }
26395 }
26396
26397 var providedEmptyOption = !!emptyOption;
26398
26399 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26400 unknownOption.val('?');
26401
26402 var options;
26403 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26404
26405
26406 var renderEmptyOption = function() {
26407 if (!providedEmptyOption) {
26408 selectElement.prepend(emptyOption);
26409 }
26410 selectElement.val('');
26411 emptyOption.prop('selected', true); // needed for IE
26412 emptyOption.attr('selected', true);
26413 };
26414
26415 var removeEmptyOption = function() {
26416 if (!providedEmptyOption) {
26417 emptyOption.remove();
26418 }
26419 };
26420
26421
26422 var renderUnknownOption = function() {
26423 selectElement.prepend(unknownOption);
26424 selectElement.val('?');
26425 unknownOption.prop('selected', true); // needed for IE
26426 unknownOption.attr('selected', true);
26427 };
26428
26429 var removeUnknownOption = function() {
26430 unknownOption.remove();
26431 };
26432
26433
26434 // Update the controller methods for multiple selectable options
26435 if (!multiple) {
26436
26437 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26438 var option = options.getOptionFromViewValue(value);
26439
26440 if (option && !option.disabled) {
26441 if (selectElement[0].value !== option.selectValue) {
26442 removeUnknownOption();
26443 removeEmptyOption();
26444
26445 selectElement[0].value = option.selectValue;
26446 option.element.selected = true;
26447 option.element.setAttribute('selected', 'selected');
26448 }
26449 } else {
26450 if (value === null || providedEmptyOption) {
26451 removeUnknownOption();
26452 renderEmptyOption();
26453 } else {
26454 removeEmptyOption();
26455 renderUnknownOption();
26456 }
26457 }
26458 };
26459
26460 selectCtrl.readValue = function readNgOptionsValue() {
26461
26462 var selectedOption = options.selectValueMap[selectElement.val()];
26463
26464 if (selectedOption && !selectedOption.disabled) {
26465 removeEmptyOption();
26466 removeUnknownOption();
26467 return options.getViewValueFromOption(selectedOption);
26468 }
26469 return null;
26470 };
26471
26472 // If we are using `track by` then we must watch the tracked value on the model
26473 // since ngModel only watches for object identity change
26474 if (ngOptions.trackBy) {
26475 scope.$watch(
26476 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26477 function() { ngModelCtrl.$render(); }
26478 );
26479 }
26480
26481 } else {
26482
26483 ngModelCtrl.$isEmpty = function(value) {
26484 return !value || value.length === 0;
26485 };
26486
26487
26488 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26489 options.items.forEach(function(option) {
26490 option.element.selected = false;
26491 });
26492
26493 if (value) {
26494 value.forEach(function(item) {
26495 var option = options.getOptionFromViewValue(item);
26496 if (option && !option.disabled) option.element.selected = true;
26497 });
26498 }
26499 };
26500
26501
26502 selectCtrl.readValue = function readNgOptionsMultiple() {
26503 var selectedValues = selectElement.val() || [],
26504 selections = [];
26505
26506 forEach(selectedValues, function(value) {
26507 var option = options.selectValueMap[value];
26508 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26509 });
26510
26511 return selections;
26512 };
26513
26514 // If we are using `track by` then we must watch these tracked values on the model
26515 // since ngModel only watches for object identity change
26516 if (ngOptions.trackBy) {
26517
26518 scope.$watchCollection(function() {
26519 if (isArray(ngModelCtrl.$viewValue)) {
26520 return ngModelCtrl.$viewValue.map(function(value) {
26521 return ngOptions.getTrackByValue(value);
26522 });
26523 }
26524 }, function() {
26525 ngModelCtrl.$render();
26526 });
26527
26528 }
26529 }
26530
26531
26532 if (providedEmptyOption) {
26533
26534 // we need to remove it before calling selectElement.empty() because otherwise IE will
26535 // remove the label from the element. wtf?
26536 emptyOption.remove();
26537
26538 // compile the element since there might be bindings in it
26539 $compile(emptyOption)(scope);
26540
26541 // remove the class, which is added automatically because we recompile the element and it
26542 // becomes the compilation root
26543 emptyOption.removeClass('ng-scope');
26544 } else {
26545 emptyOption = jqLite(optionTemplate.cloneNode(false));
26546 }
26547
26548 // We need to do this here to ensure that the options object is defined
26549 // when we first hit it in writeNgOptionsValue
26550 updateOptions();
26551
26552 // We will re-render the option elements if the option values or labels change
26553 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26554
26555 // ------------------------------------------------------------------ //
26556
26557
26558 function updateOptionElement(option, element) {
26559 option.element = element;
26560 element.disabled = option.disabled;
26561 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26562 // selects in certain circumstances when multiple selects are next to each other and display
26563 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26564 // See https://github.com/angular/angular.js/issues/11314 for more info.
26565 // This is unfortunately untestable with unit / e2e tests
26566 if (option.label !== element.label) {
26567 element.label = option.label;
26568 element.textContent = option.label;
26569 }
26570 if (option.value !== element.value) element.value = option.selectValue;
26571 }
26572
26573 function addOrReuseElement(parent, current, type, templateElement) {
26574 var element;
26575 // Check whether we can reuse the next element
26576 if (current && lowercase(current.nodeName) === type) {
26577 // The next element is the right type so reuse it
26578 element = current;
26579 } else {
26580 // The next element is not the right type so create a new one
26581 element = templateElement.cloneNode(false);
26582 if (!current) {
26583 // There are no more elements so just append it to the select
26584 parent.appendChild(element);
26585 } else {
26586 // The next element is not a group so insert the new one
26587 parent.insertBefore(element, current);
26588 }
26589 }
26590 return element;
26591 }
26592
26593
26594 function removeExcessElements(current) {
26595 var next;
26596 while (current) {
26597 next = current.nextSibling;
26598 jqLiteRemove(current);
26599 current = next;
26600 }
26601 }
26602
26603
26604 function skipEmptyAndUnknownOptions(current) {
26605 var emptyOption_ = emptyOption && emptyOption[0];
26606 var unknownOption_ = unknownOption && unknownOption[0];
26607
26608 if (emptyOption_ || unknownOption_) {
26609 while (current &&
26610 (current === emptyOption_ ||
26611 current === unknownOption_ ||
26612 emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) {
26613 // Empty options might have directives that transclude
26614 // and insert comments (e.g. ngIf)
26615 current = current.nextSibling;
26616 }
26617 }
26618 return current;
26619 }
26620
26621
26622 function updateOptions() {
26623
26624 var previousValue = options && selectCtrl.readValue();
26625
26626 options = ngOptions.getOptions();
26627
26628 var groupMap = {};
26629 var currentElement = selectElement[0].firstChild;
26630
26631 // Ensure that the empty option is always there if it was explicitly provided
26632 if (providedEmptyOption) {
26633 selectElement.prepend(emptyOption);
26634 }
26635
26636 currentElement = skipEmptyAndUnknownOptions(currentElement);
26637
26638 options.items.forEach(function updateOption(option) {
26639 var group;
26640 var groupElement;
26641 var optionElement;
26642
26643 if (option.group) {
26644
26645 // This option is to live in a group
26646 // See if we have already created this group
26647 group = groupMap[option.group];
26648
26649 if (!group) {
26650
26651 // We have not already created this group
26652 groupElement = addOrReuseElement(selectElement[0],
26653 currentElement,
26654 'optgroup',
26655 optGroupTemplate);
26656 // Move to the next element
26657 currentElement = groupElement.nextSibling;
26658
26659 // Update the label on the group element
26660 groupElement.label = option.group;
26661
26662 // Store it for use later
26663 group = groupMap[option.group] = {
26664 groupElement: groupElement,
26665 currentOptionElement: groupElement.firstChild
26666 };
26667
26668 }
26669
26670 // So now we have a group for this option we add the option to the group
26671 optionElement = addOrReuseElement(group.groupElement,
26672 group.currentOptionElement,
26673 'option',
26674 optionTemplate);
26675 updateOptionElement(option, optionElement);
26676 // Move to the next element
26677 group.currentOptionElement = optionElement.nextSibling;
26678
26679 } else {
26680
26681 // This option is not in a group
26682 optionElement = addOrReuseElement(selectElement[0],
26683 currentElement,
26684 'option',
26685 optionTemplate);
26686 updateOptionElement(option, optionElement);
26687 // Move to the next element
26688 currentElement = optionElement.nextSibling;
26689 }
26690 });
26691
26692
26693 // Now remove all excess options and group
26694 Object.keys(groupMap).forEach(function(key) {
26695 removeExcessElements(groupMap[key].currentOptionElement);
26696 });
26697 removeExcessElements(currentElement);
26698
26699 ngModelCtrl.$render();
26700
26701 // Check to see if the value has changed due to the update to the options
26702 if (!ngModelCtrl.$isEmpty(previousValue)) {
26703 var nextValue = selectCtrl.readValue();
26704 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26705 ngModelCtrl.$setViewValue(nextValue);
26706 ngModelCtrl.$render();
26707 }
26708 }
26709
26710 }
26711
26712 }
26713 };
26714}];
26715
26716/**
26717 * @ngdoc directive
26718 * @name ngPluralize
26719 * @restrict EA
26720 *
26721 * @description
26722 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26723 * These rules are bundled with angular.js, but can be overridden
26724 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26725 * by specifying the mappings between
26726 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26727 * and the strings to be displayed.
26728 *
26729 * # Plural categories and explicit number rules
26730 * There are two
26731 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26732 * in Angular's default en-US locale: "one" and "other".
26733 *
26734 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26735 * any number that is not 1), an explicit number rule can only match one number. For example, the
26736 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26737 * and explicit number rules throughout the rest of this documentation.
26738 *
26739 * # Configuring ngPluralize
26740 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26741 * You can also provide an optional attribute, `offset`.
26742 *
26743 * The value of the `count` attribute can be either a string or an {@link guide/expression
26744 * Angular expression}; these are evaluated on the current scope for its bound value.
26745 *
26746 * The `when` attribute specifies the mappings between plural categories and the actual
26747 * string to be displayed. The value of the attribute should be a JSON object.
26748 *
26749 * The following example shows how to configure ngPluralize:
26750 *
26751 * ```html
26752 * <ng-pluralize count="personCount"
26753 when="{'0': 'Nobody is viewing.',
26754 * 'one': '1 person is viewing.',
26755 * 'other': '{} people are viewing.'}">
26756 * </ng-pluralize>
26757 *```
26758 *
26759 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26760 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26761 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26762 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26763 * show "a dozen people are viewing".
26764 *
26765 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26766 * into pluralized strings. In the previous example, Angular will replace `{}` with
26767 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26768 * for <span ng-non-bindable>{{numberExpression}}</span>.
26769 *
26770 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26771 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26772 *
26773 * # Configuring ngPluralize with offset
26774 * The `offset` attribute allows further customization of pluralized text, which can result in
26775 * a better user experience. For example, instead of the message "4 people are viewing this document",
26776 * you might display "John, Kate and 2 others are viewing this document".
26777 * The offset attribute allows you to offset a number by any desired value.
26778 * Let's take a look at an example:
26779 *
26780 * ```html
26781 * <ng-pluralize count="personCount" offset=2
26782 * when="{'0': 'Nobody is viewing.',
26783 * '1': '{{person1}} is viewing.',
26784 * '2': '{{person1}} and {{person2}} are viewing.',
26785 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26786 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26787 * </ng-pluralize>
26788 * ```
26789 *
26790 * Notice that we are still using two plural categories(one, other), but we added
26791 * three explicit number rules 0, 1 and 2.
26792 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26793 * When three people view the document, no explicit number rule is found, so
26794 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26795 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26796 * is shown.
26797 *
26798 * Note that when you specify offsets, you must provide explicit number rules for
26799 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26800 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26801 * plural categories "one" and "other".
26802 *
26803 * @param {string|expression} count The variable to be bound to.
26804 * @param {string} when The mapping between plural category to its corresponding strings.
26805 * @param {number=} offset Offset to deduct from the total number.
26806 *
26807 * @example
26808 <example module="pluralizeExample">
26809 <file name="index.html">
26810 <script>
26811 angular.module('pluralizeExample', [])
26812 .controller('ExampleController', ['$scope', function($scope) {
26813 $scope.person1 = 'Igor';
26814 $scope.person2 = 'Misko';
26815 $scope.personCount = 1;
26816 }]);
26817 </script>
26818 <div ng-controller="ExampleController">
26819 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26820 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26821 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
26822
26823 <!--- Example with simple pluralization rules for en locale --->
26824 Without Offset:
26825 <ng-pluralize count="personCount"
26826 when="{'0': 'Nobody is viewing.',
26827 'one': '1 person is viewing.',
26828 'other': '{} people are viewing.'}">
26829 </ng-pluralize><br>
26830
26831 <!--- Example with offset --->
26832 With Offset(2):
26833 <ng-pluralize count="personCount" offset=2
26834 when="{'0': 'Nobody is viewing.',
26835 '1': '{{person1}} is viewing.',
26836 '2': '{{person1}} and {{person2}} are viewing.',
26837 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26838 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26839 </ng-pluralize>
26840 </div>
26841 </file>
26842 <file name="protractor.js" type="protractor">
26843 it('should show correct pluralized string', function() {
26844 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
26845 var withOffset = element.all(by.css('ng-pluralize')).get(1);
26846 var countInput = element(by.model('personCount'));
26847
26848 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
26849 expect(withOffset.getText()).toEqual('Igor is viewing.');
26850
26851 countInput.clear();
26852 countInput.sendKeys('0');
26853
26854 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
26855 expect(withOffset.getText()).toEqual('Nobody is viewing.');
26856
26857 countInput.clear();
26858 countInput.sendKeys('2');
26859
26860 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
26861 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
26862
26863 countInput.clear();
26864 countInput.sendKeys('3');
26865
26866 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
26867 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
26868
26869 countInput.clear();
26870 countInput.sendKeys('4');
26871
26872 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
26873 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
26874 });
26875 it('should show data-bound names', function() {
26876 var withOffset = element.all(by.css('ng-pluralize')).get(1);
26877 var personCount = element(by.model('personCount'));
26878 var person1 = element(by.model('person1'));
26879 var person2 = element(by.model('person2'));
26880 personCount.clear();
26881 personCount.sendKeys('4');
26882 person1.clear();
26883 person1.sendKeys('Di');
26884 person2.clear();
26885 person2.sendKeys('Vojta');
26886 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
26887 });
26888 </file>
26889 </example>
26890 */
26891var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
26892 var BRACE = /{}/g,
26893 IS_WHEN = /^when(Minus)?(.+)$/;
26894
26895 return {
26896 link: function(scope, element, attr) {
26897 var numberExp = attr.count,
26898 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
26899 offset = attr.offset || 0,
26900 whens = scope.$eval(whenExp) || {},
26901 whensExpFns = {},
26902 startSymbol = $interpolate.startSymbol(),
26903 endSymbol = $interpolate.endSymbol(),
26904 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
26905 watchRemover = angular.noop,
26906 lastCount;
26907
26908 forEach(attr, function(expression, attributeName) {
26909 var tmpMatch = IS_WHEN.exec(attributeName);
26910 if (tmpMatch) {
26911 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
26912 whens[whenKey] = element.attr(attr.$attr[attributeName]);
26913 }
26914 });
26915 forEach(whens, function(expression, key) {
26916 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
26917
26918 });
26919
26920 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
26921 var count = parseFloat(newVal);
26922 var countIsNaN = isNaN(count);
26923
26924 if (!countIsNaN && !(count in whens)) {
26925 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
26926 // Otherwise, check it against pluralization rules in $locale service.
26927 count = $locale.pluralCat(count - offset);
26928 }
26929
26930 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
26931 // In JS `NaN !== NaN`, so we have to exlicitly check.
26932 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
26933 watchRemover();
26934 var whenExpFn = whensExpFns[count];
26935 if (isUndefined(whenExpFn)) {
26936 if (newVal != null) {
26937 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
26938 }
26939 watchRemover = noop;
26940 updateElementText();
26941 } else {
26942 watchRemover = scope.$watch(whenExpFn, updateElementText);
26943 }
26944 lastCount = count;
26945 }
26946 });
26947
26948 function updateElementText(newText) {
26949 element.text(newText || '');
26950 }
26951 }
26952 };
26953}];
26954
26955/**
26956 * @ngdoc directive
26957 * @name ngRepeat
26958 * @multiElement
26959 *
26960 * @description
26961 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
26962 * instance gets its own scope, where the given loop variable is set to the current collection item,
26963 * and `$index` is set to the item index or key.
26964 *
26965 * Special properties are exposed on the local scope of each template instance, including:
26966 *
26967 * | Variable | Type | Details |
26968 * |-----------|-----------------|-----------------------------------------------------------------------------|
26969 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
26970 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
26971 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
26972 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
26973 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
26974 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
26975 *
26976 * <div class="alert alert-info">
26977 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
26978 * This may be useful when, for instance, nesting ngRepeats.
26979 * </div>
26980 *
26981 *
26982 * # Iterating over object properties
26983 *
26984 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
26985 * syntax:
26986 *
26987 * ```js
26988 * <div ng-repeat="(key, value) in myObj"> ... </div>
26989 * ```
26990 *
26991 * You need to be aware that the JavaScript specification does not define the order of keys
26992 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
26993 * used to sort the keys alphabetically.)
26994 *
26995 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
26996 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
26997 * keys in the order in which they were defined, although there are exceptions when keys are deleted
26998 * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
26999 *
27000 * If this is not desired, the recommended workaround is to convert your object into an array
27001 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27002 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27003 * or implement a `$watch` on the object yourself.
27004 *
27005 *
27006 * # Tracking and Duplicates
27007 *
27008 * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
27009 *
27010 * * When an item is added, a new instance of the template is added to the DOM.
27011 * * When an item is removed, its template instance is removed from the DOM.
27012 * * When items are reordered, their respective templates are reordered in the DOM.
27013 *
27014 * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
27015 * there are duplicates, it is not possible to maintain a one-to-one mapping between collection
27016 * items and DOM elements.
27017 *
27018 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27019 * with your own using the `track by` expression.
27020 *
27021 * For example, you may track items by the index of each item in the collection, using the
27022 * special scope property `$index`:
27023 * ```html
27024 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27025 * {{n}}
27026 * </div>
27027 * ```
27028 *
27029 * You may use arbitrary expressions in `track by`, including references to custom functions
27030 * on the scope:
27031 * ```html
27032 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27033 * {{n}}
27034 * </div>
27035 * ```
27036 *
27037 * If you are working with objects that have an identifier property, you can track
27038 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27039 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27040 * JavaScript objects in the collection have been substituted for new ones:
27041 * ```html
27042 * <div ng-repeat="model in collection track by model.id">
27043 * {{model.name}}
27044 * </div>
27045 * ```
27046 *
27047 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27048 * `$id` function, which tracks items by their identity:
27049 * ```html
27050 * <div ng-repeat="obj in collection track by $id(obj)">
27051 * {{obj.prop}}
27052 * </div>
27053 * ```
27054 *
27055 * <div class="alert alert-warning">
27056 * **Note:** `track by` must always be the last expression:
27057 * </div>
27058 * ```
27059 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27060 * {{model.name}}
27061 * </div>
27062 * ```
27063 *
27064 * # Special repeat start and end points
27065 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27066 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27067 * 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)
27068 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27069 *
27070 * The example below makes use of this feature:
27071 * ```html
27072 * <header ng-repeat-start="item in items">
27073 * Header {{ item }}
27074 * </header>
27075 * <div class="body">
27076 * Body {{ item }}
27077 * </div>
27078 * <footer ng-repeat-end>
27079 * Footer {{ item }}
27080 * </footer>
27081 * ```
27082 *
27083 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27084 * ```html
27085 * <header>
27086 * Header A
27087 * </header>
27088 * <div class="body">
27089 * Body A
27090 * </div>
27091 * <footer>
27092 * Footer A
27093 * </footer>
27094 * <header>
27095 * Header B
27096 * </header>
27097 * <div class="body">
27098 * Body B
27099 * </div>
27100 * <footer>
27101 * Footer B
27102 * </footer>
27103 * ```
27104 *
27105 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27106 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27107 *
27108 * @animations
27109 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27110 *
27111 * **.leave** - when an item is removed from the list or when an item is filtered out
27112 *
27113 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27114 *
27115 * @element ANY
27116 * @scope
27117 * @priority 1000
27118 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27119 * formats are currently supported:
27120 *
27121 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27122 * is a scope expression giving the collection to enumerate.
27123 *
27124 * For example: `album in artist.albums`.
27125 *
27126 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27127 * and `expression` is the scope expression giving the collection to enumerate.
27128 *
27129 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27130 *
27131 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27132 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27133 * is specified, ng-repeat associates elements by identity. It is an error to have
27134 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27135 * mapped to the same DOM element, which is not possible.)
27136 *
27137 * Note that the tracking expression must come last, after any filters, and the alias expression.
27138 *
27139 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27140 * will be associated by item identity in the array.
27141 *
27142 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27143 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27144 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27145 * element in the same way in the DOM.
27146 *
27147 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27148 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27149 * property is same.
27150 *
27151 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27152 * to items in conjunction with a tracking expression.
27153 *
27154 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27155 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27156 * when a filter is active on the repeater, but the filtered result set is empty.
27157 *
27158 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27159 * the items have been processed through the filter.
27160 *
27161 * 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
27162 * (and not as operator, inside an expression).
27163 *
27164 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27165 *
27166 * @example
27167 * This example initializes the scope to a list of names and
27168 * then uses `ngRepeat` to display every person:
27169 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27170 <file name="index.html">
27171 <div ng-init="friends = [
27172 {name:'John', age:25, gender:'boy'},
27173 {name:'Jessie', age:30, gender:'girl'},
27174 {name:'Johanna', age:28, gender:'girl'},
27175 {name:'Joy', age:15, gender:'girl'},
27176 {name:'Mary', age:28, gender:'girl'},
27177 {name:'Peter', age:95, gender:'boy'},
27178 {name:'Sebastian', age:50, gender:'boy'},
27179 {name:'Erika', age:27, gender:'girl'},
27180 {name:'Patrick', age:40, gender:'boy'},
27181 {name:'Samantha', age:60, gender:'girl'}
27182 ]">
27183 I have {{friends.length}} friends. They are:
27184 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27185 <ul class="example-animate-container">
27186 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27187 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27188 </li>
27189 <li class="animate-repeat" ng-if="results.length == 0">
27190 <strong>No results found...</strong>
27191 </li>
27192 </ul>
27193 </div>
27194 </file>
27195 <file name="animations.css">
27196 .example-animate-container {
27197 background:white;
27198 border:1px solid black;
27199 list-style:none;
27200 margin:0;
27201 padding:0 10px;
27202 }
27203
27204 .animate-repeat {
27205 line-height:40px;
27206 list-style:none;
27207 box-sizing:border-box;
27208 }
27209
27210 .animate-repeat.ng-move,
27211 .animate-repeat.ng-enter,
27212 .animate-repeat.ng-leave {
27213 transition:all linear 0.5s;
27214 }
27215
27216 .animate-repeat.ng-leave.ng-leave-active,
27217 .animate-repeat.ng-move,
27218 .animate-repeat.ng-enter {
27219 opacity:0;
27220 max-height:0;
27221 }
27222
27223 .animate-repeat.ng-leave,
27224 .animate-repeat.ng-move.ng-move-active,
27225 .animate-repeat.ng-enter.ng-enter-active {
27226 opacity:1;
27227 max-height:40px;
27228 }
27229 </file>
27230 <file name="protractor.js" type="protractor">
27231 var friends = element.all(by.repeater('friend in friends'));
27232
27233 it('should render initial data set', function() {
27234 expect(friends.count()).toBe(10);
27235 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27236 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27237 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27238 expect(element(by.binding('friends.length')).getText())
27239 .toMatch("I have 10 friends. They are:");
27240 });
27241
27242 it('should update repeater when filter predicate changes', function() {
27243 expect(friends.count()).toBe(10);
27244
27245 element(by.model('q')).sendKeys('ma');
27246
27247 expect(friends.count()).toBe(2);
27248 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27249 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27250 });
27251 </file>
27252 </example>
27253 */
27254var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27255 var NG_REMOVED = '$$NG_REMOVED';
27256 var ngRepeatMinErr = minErr('ngRepeat');
27257
27258 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27259 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27260 scope[valueIdentifier] = value;
27261 if (keyIdentifier) scope[keyIdentifier] = key;
27262 scope.$index = index;
27263 scope.$first = (index === 0);
27264 scope.$last = (index === (arrayLength - 1));
27265 scope.$middle = !(scope.$first || scope.$last);
27266 // jshint bitwise: false
27267 scope.$odd = !(scope.$even = (index&1) === 0);
27268 // jshint bitwise: true
27269 };
27270
27271 var getBlockStart = function(block) {
27272 return block.clone[0];
27273 };
27274
27275 var getBlockEnd = function(block) {
27276 return block.clone[block.clone.length - 1];
27277 };
27278
27279
27280 return {
27281 restrict: 'A',
27282 multiElement: true,
27283 transclude: 'element',
27284 priority: 1000,
27285 terminal: true,
27286 $$tlb: true,
27287 compile: function ngRepeatCompile($element, $attr) {
27288 var expression = $attr.ngRepeat;
27289 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27290
27291 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*$/);
27292
27293 if (!match) {
27294 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27295 expression);
27296 }
27297
27298 var lhs = match[1];
27299 var rhs = match[2];
27300 var aliasAs = match[3];
27301 var trackByExp = match[4];
27302
27303 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27304
27305 if (!match) {
27306 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27307 lhs);
27308 }
27309 var valueIdentifier = match[3] || match[1];
27310 var keyIdentifier = match[2];
27311
27312 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27313 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27314 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27315 aliasAs);
27316 }
27317
27318 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27319 var hashFnLocals = {$id: hashKey};
27320
27321 if (trackByExp) {
27322 trackByExpGetter = $parse(trackByExp);
27323 } else {
27324 trackByIdArrayFn = function(key, value) {
27325 return hashKey(value);
27326 };
27327 trackByIdObjFn = function(key) {
27328 return key;
27329 };
27330 }
27331
27332 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27333
27334 if (trackByExpGetter) {
27335 trackByIdExpFn = function(key, value, index) {
27336 // assign key, value, and $index to the locals so that they can be used in hash functions
27337 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27338 hashFnLocals[valueIdentifier] = value;
27339 hashFnLocals.$index = index;
27340 return trackByExpGetter($scope, hashFnLocals);
27341 };
27342 }
27343
27344 // Store a list of elements from previous run. This is a hash where key is the item from the
27345 // iterator, and the value is objects with following properties.
27346 // - scope: bound scope
27347 // - element: previous element.
27348 // - index: position
27349 //
27350 // We are using no-proto object so that we don't need to guard against inherited props via
27351 // hasOwnProperty.
27352 var lastBlockMap = createMap();
27353
27354 //watch props
27355 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27356 var index, length,
27357 previousNode = $element[0], // node that cloned nodes should be inserted after
27358 // initialized to the comment node anchor
27359 nextNode,
27360 // Same as lastBlockMap but it has the current state. It will become the
27361 // lastBlockMap on the next iteration.
27362 nextBlockMap = createMap(),
27363 collectionLength,
27364 key, value, // key/value of iteration
27365 trackById,
27366 trackByIdFn,
27367 collectionKeys,
27368 block, // last object information {scope, element, id}
27369 nextBlockOrder,
27370 elementsToRemove;
27371
27372 if (aliasAs) {
27373 $scope[aliasAs] = collection;
27374 }
27375
27376 if (isArrayLike(collection)) {
27377 collectionKeys = collection;
27378 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27379 } else {
27380 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27381 // if object, extract keys, in enumeration order, unsorted
27382 collectionKeys = [];
27383 for (var itemKey in collection) {
27384 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27385 collectionKeys.push(itemKey);
27386 }
27387 }
27388 }
27389
27390 collectionLength = collectionKeys.length;
27391 nextBlockOrder = new Array(collectionLength);
27392
27393 // locate existing items
27394 for (index = 0; index < collectionLength; index++) {
27395 key = (collection === collectionKeys) ? index : collectionKeys[index];
27396 value = collection[key];
27397 trackById = trackByIdFn(key, value, index);
27398 if (lastBlockMap[trackById]) {
27399 // found previously seen block
27400 block = lastBlockMap[trackById];
27401 delete lastBlockMap[trackById];
27402 nextBlockMap[trackById] = block;
27403 nextBlockOrder[index] = block;
27404 } else if (nextBlockMap[trackById]) {
27405 // if collision detected. restore lastBlockMap and throw an error
27406 forEach(nextBlockOrder, function(block) {
27407 if (block && block.scope) lastBlockMap[block.id] = block;
27408 });
27409 throw ngRepeatMinErr('dupes',
27410 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27411 expression, trackById, value);
27412 } else {
27413 // new never before seen block
27414 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27415 nextBlockMap[trackById] = true;
27416 }
27417 }
27418
27419 // remove leftover items
27420 for (var blockKey in lastBlockMap) {
27421 block = lastBlockMap[blockKey];
27422 elementsToRemove = getBlockNodes(block.clone);
27423 $animate.leave(elementsToRemove);
27424 if (elementsToRemove[0].parentNode) {
27425 // if the element was not removed yet because of pending animation, mark it as deleted
27426 // so that we can ignore it later
27427 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27428 elementsToRemove[index][NG_REMOVED] = true;
27429 }
27430 }
27431 block.scope.$destroy();
27432 }
27433
27434 // we are not using forEach for perf reasons (trying to avoid #call)
27435 for (index = 0; index < collectionLength; index++) {
27436 key = (collection === collectionKeys) ? index : collectionKeys[index];
27437 value = collection[key];
27438 block = nextBlockOrder[index];
27439
27440 if (block.scope) {
27441 // if we have already seen this object, then we need to reuse the
27442 // associated scope/element
27443
27444 nextNode = previousNode;
27445
27446 // skip nodes that are already pending removal via leave animation
27447 do {
27448 nextNode = nextNode.nextSibling;
27449 } while (nextNode && nextNode[NG_REMOVED]);
27450
27451 if (getBlockStart(block) != nextNode) {
27452 // existing item which got moved
27453 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27454 }
27455 previousNode = getBlockEnd(block);
27456 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27457 } else {
27458 // new item which we don't know about
27459 $transclude(function ngRepeatTransclude(clone, scope) {
27460 block.scope = scope;
27461 // http://jsperf.com/clone-vs-createcomment
27462 var endNode = ngRepeatEndComment.cloneNode(false);
27463 clone[clone.length++] = endNode;
27464
27465 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27466 $animate.enter(clone, null, jqLite(previousNode));
27467 previousNode = endNode;
27468 // Note: We only need the first/last node of the cloned nodes.
27469 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27470 // by a directive with templateUrl when its template arrives.
27471 block.clone = clone;
27472 nextBlockMap[block.id] = block;
27473 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27474 });
27475 }
27476 }
27477 lastBlockMap = nextBlockMap;
27478 });
27479 };
27480 }
27481 };
27482}];
27483
27484var NG_HIDE_CLASS = 'ng-hide';
27485var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27486/**
27487 * @ngdoc directive
27488 * @name ngShow
27489 * @multiElement
27490 *
27491 * @description
27492 * The `ngShow` directive shows or hides the given HTML element based on the expression
27493 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27494 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27495 * in AngularJS and sets the display style to none (using an !important flag).
27496 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27497 *
27498 * ```html
27499 * <!-- when $scope.myValue is truthy (element is visible) -->
27500 * <div ng-show="myValue"></div>
27501 *
27502 * <!-- when $scope.myValue is falsy (element is hidden) -->
27503 * <div ng-show="myValue" class="ng-hide"></div>
27504 * ```
27505 *
27506 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27507 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27508 * from the element causing the element not to appear hidden.
27509 *
27510 * ## Why is !important used?
27511 *
27512 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27513 * can be easily overridden by heavier selectors. For example, something as simple
27514 * as changing the display style on a HTML list item would make hidden elements appear visible.
27515 * This also becomes a bigger issue when dealing with CSS frameworks.
27516 *
27517 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27518 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27519 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27520 *
27521 * ### Overriding `.ng-hide`
27522 *
27523 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27524 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27525 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27526 * with extra animation classes that can be added.
27527 *
27528 * ```css
27529 * .ng-hide:not(.ng-hide-animate) {
27530 * /&#42; this is just another form of hiding an element &#42;/
27531 * display: block!important;
27532 * position: absolute;
27533 * top: -9999px;
27534 * left: -9999px;
27535 * }
27536 * ```
27537 *
27538 * By default you don't need to override in CSS anything and the animations will work around the display style.
27539 *
27540 * ## A note about animations with `ngShow`
27541 *
27542 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27543 * is true and false. This system works like the animation system present with ngClass except that
27544 * you must also include the !important flag to override the display property
27545 * so that you can perform an animation when the element is hidden during the time of the animation.
27546 *
27547 * ```css
27548 * //
27549 * //a working example can be found at the bottom of this page
27550 * //
27551 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27552 * /&#42; this is required as of 1.3x to properly
27553 * apply all styling in a show/hide animation &#42;/
27554 * transition: 0s linear all;
27555 * }
27556 *
27557 * .my-element.ng-hide-add-active,
27558 * .my-element.ng-hide-remove-active {
27559 * /&#42; the transition is defined in the active class &#42;/
27560 * transition: 1s linear all;
27561 * }
27562 *
27563 * .my-element.ng-hide-add { ... }
27564 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27565 * .my-element.ng-hide-remove { ... }
27566 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27567 * ```
27568 *
27569 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27570 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27571 *
27572 * @animations
27573 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27574 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27575 *
27576 * @element ANY
27577 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27578 * then the element is shown or hidden respectively.
27579 *
27580 * @example
27581 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27582 <file name="index.html">
27583 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27584 <div>
27585 Show:
27586 <div class="check-element animate-show" ng-show="checked">
27587 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27588 </div>
27589 </div>
27590 <div>
27591 Hide:
27592 <div class="check-element animate-show" ng-hide="checked">
27593 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27594 </div>
27595 </div>
27596 </file>
27597 <file name="glyphicons.css">
27598 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27599 </file>
27600 <file name="animations.css">
27601 .animate-show {
27602 line-height: 20px;
27603 opacity: 1;
27604 padding: 10px;
27605 border: 1px solid black;
27606 background: white;
27607 }
27608
27609 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27610 transition: all linear 0.5s;
27611 }
27612
27613 .animate-show.ng-hide {
27614 line-height: 0;
27615 opacity: 0;
27616 padding: 0 10px;
27617 }
27618
27619 .check-element {
27620 padding: 10px;
27621 border: 1px solid black;
27622 background: white;
27623 }
27624 </file>
27625 <file name="protractor.js" type="protractor">
27626 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27627 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27628
27629 it('should check ng-show / ng-hide', function() {
27630 expect(thumbsUp.isDisplayed()).toBeFalsy();
27631 expect(thumbsDown.isDisplayed()).toBeTruthy();
27632
27633 element(by.model('checked')).click();
27634
27635 expect(thumbsUp.isDisplayed()).toBeTruthy();
27636 expect(thumbsDown.isDisplayed()).toBeFalsy();
27637 });
27638 </file>
27639 </example>
27640 */
27641var ngShowDirective = ['$animate', function($animate) {
27642 return {
27643 restrict: 'A',
27644 multiElement: true,
27645 link: function(scope, element, attr) {
27646 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27647 // we're adding a temporary, animation-specific class for ng-hide since this way
27648 // we can control when the element is actually displayed on screen without having
27649 // to have a global/greedy CSS selector that breaks when other animations are run.
27650 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27651 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27652 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27653 });
27654 });
27655 }
27656 };
27657}];
27658
27659
27660/**
27661 * @ngdoc directive
27662 * @name ngHide
27663 * @multiElement
27664 *
27665 * @description
27666 * The `ngHide` directive shows or hides the given HTML element based on the expression
27667 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27668 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27669 * in AngularJS and sets the display style to none (using an !important flag).
27670 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27671 *
27672 * ```html
27673 * <!-- when $scope.myValue is truthy (element is hidden) -->
27674 * <div ng-hide="myValue" class="ng-hide"></div>
27675 *
27676 * <!-- when $scope.myValue is falsy (element is visible) -->
27677 * <div ng-hide="myValue"></div>
27678 * ```
27679 *
27680 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27681 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27682 * from the element causing the element not to appear hidden.
27683 *
27684 * ## Why is !important used?
27685 *
27686 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27687 * can be easily overridden by heavier selectors. For example, something as simple
27688 * as changing the display style on a HTML list item would make hidden elements appear visible.
27689 * This also becomes a bigger issue when dealing with CSS frameworks.
27690 *
27691 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27692 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27693 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27694 *
27695 * ### Overriding `.ng-hide`
27696 *
27697 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27698 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27699 * class in CSS:
27700 *
27701 * ```css
27702 * .ng-hide {
27703 * /&#42; this is just another form of hiding an element &#42;/
27704 * display: block!important;
27705 * position: absolute;
27706 * top: -9999px;
27707 * left: -9999px;
27708 * }
27709 * ```
27710 *
27711 * By default you don't need to override in CSS anything and the animations will work around the display style.
27712 *
27713 * ## A note about animations with `ngHide`
27714 *
27715 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27716 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27717 * CSS class is added and removed for you instead of your own CSS class.
27718 *
27719 * ```css
27720 * //
27721 * //a working example can be found at the bottom of this page
27722 * //
27723 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27724 * transition: 0.5s linear all;
27725 * }
27726 *
27727 * .my-element.ng-hide-add { ... }
27728 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27729 * .my-element.ng-hide-remove { ... }
27730 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27731 * ```
27732 *
27733 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27734 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27735 *
27736 * @animations
27737 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27738 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27739 *
27740 * @element ANY
27741 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27742 * the element is shown or hidden respectively.
27743 *
27744 * @example
27745 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27746 <file name="index.html">
27747 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27748 <div>
27749 Show:
27750 <div class="check-element animate-hide" ng-show="checked">
27751 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27752 </div>
27753 </div>
27754 <div>
27755 Hide:
27756 <div class="check-element animate-hide" ng-hide="checked">
27757 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27758 </div>
27759 </div>
27760 </file>
27761 <file name="glyphicons.css">
27762 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27763 </file>
27764 <file name="animations.css">
27765 .animate-hide {
27766 transition: all linear 0.5s;
27767 line-height: 20px;
27768 opacity: 1;
27769 padding: 10px;
27770 border: 1px solid black;
27771 background: white;
27772 }
27773
27774 .animate-hide.ng-hide {
27775 line-height: 0;
27776 opacity: 0;
27777 padding: 0 10px;
27778 }
27779
27780 .check-element {
27781 padding: 10px;
27782 border: 1px solid black;
27783 background: white;
27784 }
27785 </file>
27786 <file name="protractor.js" type="protractor">
27787 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27788 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27789
27790 it('should check ng-show / ng-hide', function() {
27791 expect(thumbsUp.isDisplayed()).toBeFalsy();
27792 expect(thumbsDown.isDisplayed()).toBeTruthy();
27793
27794 element(by.model('checked')).click();
27795
27796 expect(thumbsUp.isDisplayed()).toBeTruthy();
27797 expect(thumbsDown.isDisplayed()).toBeFalsy();
27798 });
27799 </file>
27800 </example>
27801 */
27802var ngHideDirective = ['$animate', function($animate) {
27803 return {
27804 restrict: 'A',
27805 multiElement: true,
27806 link: function(scope, element, attr) {
27807 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27808 // The comment inside of the ngShowDirective explains why we add and
27809 // remove a temporary class for the show/hide animation
27810 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27811 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27812 });
27813 });
27814 }
27815 };
27816}];
27817
27818/**
27819 * @ngdoc directive
27820 * @name ngStyle
27821 * @restrict AC
27822 *
27823 * @description
27824 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
27825 *
27826 * @element ANY
27827 * @param {expression} ngStyle
27828 *
27829 * {@link guide/expression Expression} which evals to an
27830 * object whose keys are CSS style names and values are corresponding values for those CSS
27831 * keys.
27832 *
27833 * Since some CSS style names are not valid keys for an object, they must be quoted.
27834 * See the 'background-color' style in the example below.
27835 *
27836 * @example
27837 <example>
27838 <file name="index.html">
27839 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
27840 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
27841 <input type="button" value="clear" ng-click="myStyle={}">
27842 <br/>
27843 <span ng-style="myStyle">Sample Text</span>
27844 <pre>myStyle={{myStyle}}</pre>
27845 </file>
27846 <file name="style.css">
27847 span {
27848 color: black;
27849 }
27850 </file>
27851 <file name="protractor.js" type="protractor">
27852 var colorSpan = element(by.css('span'));
27853
27854 it('should check ng-style', function() {
27855 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
27856 element(by.css('input[value=\'set color\']')).click();
27857 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
27858 element(by.css('input[value=clear]')).click();
27859 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
27860 });
27861 </file>
27862 </example>
27863 */
27864var ngStyleDirective = ngDirective(function(scope, element, attr) {
27865 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
27866 if (oldStyles && (newStyles !== oldStyles)) {
27867 forEach(oldStyles, function(val, style) { element.css(style, '');});
27868 }
27869 if (newStyles) element.css(newStyles);
27870 }, true);
27871});
27872
27873/**
27874 * @ngdoc directive
27875 * @name ngSwitch
27876 * @restrict EA
27877 *
27878 * @description
27879 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
27880 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
27881 * as specified in the template.
27882 *
27883 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
27884 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
27885 * matches the value obtained from the evaluated expression. In other words, you define a container element
27886 * (where you place the directive), place an expression on the **`on="..."` attribute**
27887 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
27888 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
27889 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
27890 * attribute is displayed.
27891 *
27892 * <div class="alert alert-info">
27893 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
27894 * as literal string values to match against.
27895 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
27896 * value of the expression `$scope.someVal`.
27897 * </div>
27898
27899 * @animations
27900 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
27901 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
27902 *
27903 * @usage
27904 *
27905 * ```
27906 * <ANY ng-switch="expression">
27907 * <ANY ng-switch-when="matchValue1">...</ANY>
27908 * <ANY ng-switch-when="matchValue2">...</ANY>
27909 * <ANY ng-switch-default>...</ANY>
27910 * </ANY>
27911 * ```
27912 *
27913 *
27914 * @scope
27915 * @priority 1200
27916 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
27917 * On child elements add:
27918 *
27919 * * `ngSwitchWhen`: the case statement to match against. If match then this
27920 * case will be displayed. If the same match appears multiple times, all the
27921 * elements will be displayed.
27922 * * `ngSwitchDefault`: the default case when no other case match. If there
27923 * are multiple default cases, all of them will be displayed when no other
27924 * case match.
27925 *
27926 *
27927 * @example
27928 <example module="switchExample" deps="angular-animate.js" animations="true">
27929 <file name="index.html">
27930 <div ng-controller="ExampleController">
27931 <select ng-model="selection" ng-options="item for item in items">
27932 </select>
27933 <code>selection={{selection}}</code>
27934 <hr/>
27935 <div class="animate-switch-container"
27936 ng-switch on="selection">
27937 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
27938 <div class="animate-switch" ng-switch-when="home">Home Span</div>
27939 <div class="animate-switch" ng-switch-default>default</div>
27940 </div>
27941 </div>
27942 </file>
27943 <file name="script.js">
27944 angular.module('switchExample', ['ngAnimate'])
27945 .controller('ExampleController', ['$scope', function($scope) {
27946 $scope.items = ['settings', 'home', 'other'];
27947 $scope.selection = $scope.items[0];
27948 }]);
27949 </file>
27950 <file name="animations.css">
27951 .animate-switch-container {
27952 position:relative;
27953 background:white;
27954 border:1px solid black;
27955 height:40px;
27956 overflow:hidden;
27957 }
27958
27959 .animate-switch {
27960 padding:10px;
27961 }
27962
27963 .animate-switch.ng-animate {
27964 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
27965
27966 position:absolute;
27967 top:0;
27968 left:0;
27969 right:0;
27970 bottom:0;
27971 }
27972
27973 .animate-switch.ng-leave.ng-leave-active,
27974 .animate-switch.ng-enter {
27975 top:-50px;
27976 }
27977 .animate-switch.ng-leave,
27978 .animate-switch.ng-enter.ng-enter-active {
27979 top:0;
27980 }
27981 </file>
27982 <file name="protractor.js" type="protractor">
27983 var switchElem = element(by.css('[ng-switch]'));
27984 var select = element(by.model('selection'));
27985
27986 it('should start in settings', function() {
27987 expect(switchElem.getText()).toMatch(/Settings Div/);
27988 });
27989 it('should change to home', function() {
27990 select.all(by.css('option')).get(1).click();
27991 expect(switchElem.getText()).toMatch(/Home Span/);
27992 });
27993 it('should select default', function() {
27994 select.all(by.css('option')).get(2).click();
27995 expect(switchElem.getText()).toMatch(/default/);
27996 });
27997 </file>
27998 </example>
27999 */
28000var ngSwitchDirective = ['$animate', function($animate) {
28001 return {
28002 require: 'ngSwitch',
28003
28004 // asks for $scope to fool the BC controller module
28005 controller: ['$scope', function ngSwitchController() {
28006 this.cases = {};
28007 }],
28008 link: function(scope, element, attr, ngSwitchController) {
28009 var watchExpr = attr.ngSwitch || attr.on,
28010 selectedTranscludes = [],
28011 selectedElements = [],
28012 previousLeaveAnimations = [],
28013 selectedScopes = [];
28014
28015 var spliceFactory = function(array, index) {
28016 return function() { array.splice(index, 1); };
28017 };
28018
28019 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28020 var i, ii;
28021 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28022 $animate.cancel(previousLeaveAnimations[i]);
28023 }
28024 previousLeaveAnimations.length = 0;
28025
28026 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28027 var selected = getBlockNodes(selectedElements[i].clone);
28028 selectedScopes[i].$destroy();
28029 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28030 promise.then(spliceFactory(previousLeaveAnimations, i));
28031 }
28032
28033 selectedElements.length = 0;
28034 selectedScopes.length = 0;
28035
28036 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28037 forEach(selectedTranscludes, function(selectedTransclude) {
28038 selectedTransclude.transclude(function(caseElement, selectedScope) {
28039 selectedScopes.push(selectedScope);
28040 var anchor = selectedTransclude.element;
28041 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28042 var block = { clone: caseElement };
28043
28044 selectedElements.push(block);
28045 $animate.enter(caseElement, anchor.parent(), anchor);
28046 });
28047 });
28048 }
28049 });
28050 }
28051 };
28052}];
28053
28054var ngSwitchWhenDirective = ngDirective({
28055 transclude: 'element',
28056 priority: 1200,
28057 require: '^ngSwitch',
28058 multiElement: true,
28059 link: function(scope, element, attrs, ctrl, $transclude) {
28060 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28061 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28062 }
28063});
28064
28065var ngSwitchDefaultDirective = ngDirective({
28066 transclude: 'element',
28067 priority: 1200,
28068 require: '^ngSwitch',
28069 multiElement: true,
28070 link: function(scope, element, attr, ctrl, $transclude) {
28071 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28072 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28073 }
28074});
28075
28076/**
28077 * @ngdoc directive
28078 * @name ngTransclude
28079 * @restrict EAC
28080 *
28081 * @description
28082 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28083 *
28084 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28085 *
28086 * @element ANY
28087 *
28088 * @example
28089 <example module="transcludeExample">
28090 <file name="index.html">
28091 <script>
28092 angular.module('transcludeExample', [])
28093 .directive('pane', function(){
28094 return {
28095 restrict: 'E',
28096 transclude: true,
28097 scope: { title:'@' },
28098 template: '<div style="border: 1px solid black;">' +
28099 '<div style="background-color: gray">{{title}}</div>' +
28100 '<ng-transclude></ng-transclude>' +
28101 '</div>'
28102 };
28103 })
28104 .controller('ExampleController', ['$scope', function($scope) {
28105 $scope.title = 'Lorem Ipsum';
28106 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28107 }]);
28108 </script>
28109 <div ng-controller="ExampleController">
28110 <input ng-model="title" aria-label="title"> <br/>
28111 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28112 <pane title="{{title}}">{{text}}</pane>
28113 </div>
28114 </file>
28115 <file name="protractor.js" type="protractor">
28116 it('should have transcluded', function() {
28117 var titleElement = element(by.model('title'));
28118 titleElement.clear();
28119 titleElement.sendKeys('TITLE');
28120 var textElement = element(by.model('text'));
28121 textElement.clear();
28122 textElement.sendKeys('TEXT');
28123 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28124 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28125 });
28126 </file>
28127 </example>
28128 *
28129 */
28130var ngTranscludeDirective = ngDirective({
28131 restrict: 'EAC',
28132 link: function($scope, $element, $attrs, controller, $transclude) {
28133 if (!$transclude) {
28134 throw minErr('ngTransclude')('orphan',
28135 'Illegal use of ngTransclude directive in the template! ' +
28136 'No parent directive that requires a transclusion found. ' +
28137 'Element: {0}',
28138 startingTag($element));
28139 }
28140
28141 $transclude(function(clone) {
28142 $element.empty();
28143 $element.append(clone);
28144 });
28145 }
28146});
28147
28148/**
28149 * @ngdoc directive
28150 * @name script
28151 * @restrict E
28152 *
28153 * @description
28154 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28155 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28156 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28157 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28158 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28159 *
28160 * @param {string} type Must be set to `'text/ng-template'`.
28161 * @param {string} id Cache name of the template.
28162 *
28163 * @example
28164 <example>
28165 <file name="index.html">
28166 <script type="text/ng-template" id="/tpl.html">
28167 Content of the template.
28168 </script>
28169
28170 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28171 <div id="tpl-content" ng-include src="currentTpl"></div>
28172 </file>
28173 <file name="protractor.js" type="protractor">
28174 it('should load template defined inside script tag', function() {
28175 element(by.css('#tpl-link')).click();
28176 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28177 });
28178 </file>
28179 </example>
28180 */
28181var scriptDirective = ['$templateCache', function($templateCache) {
28182 return {
28183 restrict: 'E',
28184 terminal: true,
28185 compile: function(element, attr) {
28186 if (attr.type == 'text/ng-template') {
28187 var templateUrl = attr.id,
28188 text = element[0].text;
28189
28190 $templateCache.put(templateUrl, text);
28191 }
28192 }
28193 };
28194}];
28195
28196var noopNgModelController = { $setViewValue: noop, $render: noop };
28197
28198/**
28199 * @ngdoc type
28200 * @name select.SelectController
28201 * @description
28202 * The controller for the `<select>` directive. This provides support for reading
28203 * and writing the selected value(s) of the control and also coordinates dynamically
28204 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28205 */
28206var SelectController =
28207 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28208
28209 var self = this,
28210 optionsMap = new HashMap();
28211
28212 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28213 self.ngModelCtrl = noopNgModelController;
28214
28215 // The "unknown" option is one that is prepended to the list if the viewValue
28216 // does not match any of the options. When it is rendered the value of the unknown
28217 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28218 //
28219 // We can't just jqLite('<option>') since jqLite is not smart enough
28220 // to create it in <select> and IE barfs otherwise.
28221 self.unknownOption = jqLite(document.createElement('option'));
28222 self.renderUnknownOption = function(val) {
28223 var unknownVal = '? ' + hashKey(val) + ' ?';
28224 self.unknownOption.val(unknownVal);
28225 $element.prepend(self.unknownOption);
28226 $element.val(unknownVal);
28227 };
28228
28229 $scope.$on('$destroy', function() {
28230 // disable unknown option so that we don't do work when the whole select is being destroyed
28231 self.renderUnknownOption = noop;
28232 });
28233
28234 self.removeUnknownOption = function() {
28235 if (self.unknownOption.parent()) self.unknownOption.remove();
28236 };
28237
28238
28239 // Read the value of the select control, the implementation of this changes depending
28240 // upon whether the select can have multiple values and whether ngOptions is at work.
28241 self.readValue = function readSingleValue() {
28242 self.removeUnknownOption();
28243 return $element.val();
28244 };
28245
28246
28247 // Write the value to the select control, the implementation of this changes depending
28248 // upon whether the select can have multiple values and whether ngOptions is at work.
28249 self.writeValue = function writeSingleValue(value) {
28250 if (self.hasOption(value)) {
28251 self.removeUnknownOption();
28252 $element.val(value);
28253 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28254 } else {
28255 if (value == null && self.emptyOption) {
28256 self.removeUnknownOption();
28257 $element.val('');
28258 } else {
28259 self.renderUnknownOption(value);
28260 }
28261 }
28262 };
28263
28264
28265 // Tell the select control that an option, with the given value, has been added
28266 self.addOption = function(value, element) {
28267 assertNotHasOwnProperty(value, '"option value"');
28268 if (value === '') {
28269 self.emptyOption = element;
28270 }
28271 var count = optionsMap.get(value) || 0;
28272 optionsMap.put(value, count + 1);
28273 };
28274
28275 // Tell the select control that an option, with the given value, has been removed
28276 self.removeOption = function(value) {
28277 var count = optionsMap.get(value);
28278 if (count) {
28279 if (count === 1) {
28280 optionsMap.remove(value);
28281 if (value === '') {
28282 self.emptyOption = undefined;
28283 }
28284 } else {
28285 optionsMap.put(value, count - 1);
28286 }
28287 }
28288 };
28289
28290 // Check whether the select control has an option matching the given value
28291 self.hasOption = function(value) {
28292 return !!optionsMap.get(value);
28293 };
28294}];
28295
28296/**
28297 * @ngdoc directive
28298 * @name select
28299 * @restrict E
28300 *
28301 * @description
28302 * HTML `SELECT` element with angular data-binding.
28303 *
28304 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28305 * between the scope and the `<select>` control (including setting default values).
28306 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28307 * {@link ngOptions `ngOptions`} directives.
28308 *
28309 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28310 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28311 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28312 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28313 *
28314 * <div class="alert alert-warning">
28315 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28316 * When the model needs to be bound to a non-string value, you must either explictly convert it
28317 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28318 * This is because an option element can only be bound to string values at present.
28319 * </div>
28320 *
28321 * If the viewValue of `ngModel` does not match any of the options, then the control
28322 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28323 *
28324 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28325 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28326 * option. See example below for demonstration.
28327 *
28328 * <div class="alert alert-info">
28329 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28330 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28331 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28332 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28333 * a new scope for each repeated instance.
28334 * </div>
28335 *
28336 *
28337 * @param {string} ngModel Assignable angular expression to data-bind to.
28338 * @param {string=} name Property name of the form under which the control is published.
28339 * @param {string=} required Sets `required` validation error key if the value is not entered.
28340 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28341 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28342 * when you want to data-bind to the required attribute.
28343 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28344 * interaction with the select element.
28345 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28346 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28347 *
28348 * @example
28349 * ### Simple `select` elements with static options
28350 *
28351 * <example name="static-select" module="staticSelect">
28352 * <file name="index.html">
28353 * <div ng-controller="ExampleController">
28354 * <form name="myForm">
28355 * <label for="singleSelect"> Single select: </label><br>
28356 * <select name="singleSelect" ng-model="data.singleSelect">
28357 * <option value="option-1">Option 1</option>
28358 * <option value="option-2">Option 2</option>
28359 * </select><br>
28360 *
28361 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28362 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28363 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28364 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28365 * <option value="option-2">Option 2</option>
28366 * </select><br>
28367 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28368 * <tt>singleSelect = {{data.singleSelect}}</tt>
28369 *
28370 * <hr>
28371 * <label for="multipleSelect"> Multiple select: </label><br>
28372 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28373 * <option value="option-1">Option 1</option>
28374 * <option value="option-2">Option 2</option>
28375 * <option value="option-3">Option 3</option>
28376 * </select><br>
28377 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28378 * </form>
28379 * </div>
28380 * </file>
28381 * <file name="app.js">
28382 * angular.module('staticSelect', [])
28383 * .controller('ExampleController', ['$scope', function($scope) {
28384 * $scope.data = {
28385 * singleSelect: null,
28386 * multipleSelect: [],
28387 * option1: 'option-1',
28388 * };
28389 *
28390 * $scope.forceUnknownOption = function() {
28391 * $scope.data.singleSelect = 'nonsense';
28392 * };
28393 * }]);
28394 * </file>
28395 *</example>
28396 *
28397 * ### Using `ngRepeat` to generate `select` options
28398 * <example name="ngrepeat-select" module="ngrepeatSelect">
28399 * <file name="index.html">
28400 * <div ng-controller="ExampleController">
28401 * <form name="myForm">
28402 * <label for="repeatSelect"> Repeat select: </label>
28403 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28404 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28405 * </select>
28406 * </form>
28407 * <hr>
28408 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28409 * </div>
28410 * </file>
28411 * <file name="app.js">
28412 * angular.module('ngrepeatSelect', [])
28413 * .controller('ExampleController', ['$scope', function($scope) {
28414 * $scope.data = {
28415 * repeatSelect: null,
28416 * availableOptions: [
28417 * {id: '1', name: 'Option A'},
28418 * {id: '2', name: 'Option B'},
28419 * {id: '3', name: 'Option C'}
28420 * ],
28421 * };
28422 * }]);
28423 * </file>
28424 *</example>
28425 *
28426 *
28427 * ### Using `select` with `ngOptions` and setting a default value
28428 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28429 *
28430 * <example name="select-with-default-values" module="defaultValueSelect">
28431 * <file name="index.html">
28432 * <div ng-controller="ExampleController">
28433 * <form name="myForm">
28434 * <label for="mySelect">Make a choice:</label>
28435 * <select name="mySelect" id="mySelect"
28436 * ng-options="option.name for option in data.availableOptions track by option.id"
28437 * ng-model="data.selectedOption"></select>
28438 * </form>
28439 * <hr>
28440 * <tt>option = {{data.selectedOption}}</tt><br/>
28441 * </div>
28442 * </file>
28443 * <file name="app.js">
28444 * angular.module('defaultValueSelect', [])
28445 * .controller('ExampleController', ['$scope', function($scope) {
28446 * $scope.data = {
28447 * availableOptions: [
28448 * {id: '1', name: 'Option A'},
28449 * {id: '2', name: 'Option B'},
28450 * {id: '3', name: 'Option C'}
28451 * ],
28452 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28453 * };
28454 * }]);
28455 * </file>
28456 *</example>
28457 *
28458 *
28459 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28460 *
28461 * <example name="select-with-non-string-options" module="nonStringSelect">
28462 * <file name="index.html">
28463 * <select ng-model="model.id" convert-to-number>
28464 * <option value="0">Zero</option>
28465 * <option value="1">One</option>
28466 * <option value="2">Two</option>
28467 * </select>
28468 * {{ model }}
28469 * </file>
28470 * <file name="app.js">
28471 * angular.module('nonStringSelect', [])
28472 * .run(function($rootScope) {
28473 * $rootScope.model = { id: 2 };
28474 * })
28475 * .directive('convertToNumber', function() {
28476 * return {
28477 * require: 'ngModel',
28478 * link: function(scope, element, attrs, ngModel) {
28479 * ngModel.$parsers.push(function(val) {
28480 * return parseInt(val, 10);
28481 * });
28482 * ngModel.$formatters.push(function(val) {
28483 * return '' + val;
28484 * });
28485 * }
28486 * };
28487 * });
28488 * </file>
28489 * <file name="protractor.js" type="protractor">
28490 * it('should initialize to model', function() {
28491 * var select = element(by.css('select'));
28492 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28493 * });
28494 * </file>
28495 * </example>
28496 *
28497 */
28498var selectDirective = function() {
28499
28500 return {
28501 restrict: 'E',
28502 require: ['select', '?ngModel'],
28503 controller: SelectController,
28504 link: function(scope, element, attr, ctrls) {
28505
28506 // if ngModel is not defined, we don't need to do anything
28507 var ngModelCtrl = ctrls[1];
28508 if (!ngModelCtrl) return;
28509
28510 var selectCtrl = ctrls[0];
28511
28512 selectCtrl.ngModelCtrl = ngModelCtrl;
28513
28514 // We delegate rendering to the `writeValue` method, which can be changed
28515 // if the select can have multiple selected values or if the options are being
28516 // generated by `ngOptions`
28517 ngModelCtrl.$render = function() {
28518 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28519 };
28520
28521 // When the selected item(s) changes we delegate getting the value of the select control
28522 // to the `readValue` method, which can be changed if the select can have multiple
28523 // selected values or if the options are being generated by `ngOptions`
28524 element.on('change', function() {
28525 scope.$apply(function() {
28526 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28527 });
28528 });
28529
28530 // If the select allows multiple values then we need to modify how we read and write
28531 // values from and to the control; also what it means for the value to be empty and
28532 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28533 // doesn't trigger rendering if only an item in the array changes.
28534 if (attr.multiple) {
28535
28536 // Read value now needs to check each option to see if it is selected
28537 selectCtrl.readValue = function readMultipleValue() {
28538 var array = [];
28539 forEach(element.find('option'), function(option) {
28540 if (option.selected) {
28541 array.push(option.value);
28542 }
28543 });
28544 return array;
28545 };
28546
28547 // Write value now needs to set the selected property of each matching option
28548 selectCtrl.writeValue = function writeMultipleValue(value) {
28549 var items = new HashMap(value);
28550 forEach(element.find('option'), function(option) {
28551 option.selected = isDefined(items.get(option.value));
28552 });
28553 };
28554
28555 // we have to do it on each watch since ngModel watches reference, but
28556 // we need to work of an array, so we need to see if anything was inserted/removed
28557 var lastView, lastViewRef = NaN;
28558 scope.$watch(function selectMultipleWatch() {
28559 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28560 lastView = shallowCopy(ngModelCtrl.$viewValue);
28561 ngModelCtrl.$render();
28562 }
28563 lastViewRef = ngModelCtrl.$viewValue;
28564 });
28565
28566 // If we are a multiple select then value is now a collection
28567 // so the meaning of $isEmpty changes
28568 ngModelCtrl.$isEmpty = function(value) {
28569 return !value || value.length === 0;
28570 };
28571
28572 }
28573 }
28574 };
28575};
28576
28577
28578// The option directive is purely designed to communicate the existence (or lack of)
28579// of dynamically created (and destroyed) option elements to their containing select
28580// directive via its controller.
28581var optionDirective = ['$interpolate', function($interpolate) {
28582
28583 function chromeHack(optionElement) {
28584 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28585 // Adding an <option selected="selected"> element to a <select required="required"> should
28586 // automatically select the new element
28587 if (optionElement[0].hasAttribute('selected')) {
28588 optionElement[0].selected = true;
28589 }
28590 }
28591
28592 return {
28593 restrict: 'E',
28594 priority: 100,
28595 compile: function(element, attr) {
28596
28597 if (isDefined(attr.value)) {
28598 // If the value attribute is defined, check if it contains an interpolation
28599 var valueInterpolated = $interpolate(attr.value, true);
28600 } else {
28601 // If the value attribute is not defined then we fall back to the
28602 // text content of the option element, which may be interpolated
28603 var interpolateFn = $interpolate(element.text(), true);
28604 if (!interpolateFn) {
28605 attr.$set('value', element.text());
28606 }
28607 }
28608
28609 return function(scope, element, attr) {
28610
28611 // This is an optimization over using ^^ since we don't want to have to search
28612 // all the way to the root of the DOM for every single option element
28613 var selectCtrlName = '$selectController',
28614 parent = element.parent(),
28615 selectCtrl = parent.data(selectCtrlName) ||
28616 parent.parent().data(selectCtrlName); // in case we are in optgroup
28617
28618 function addOption(optionValue) {
28619 selectCtrl.addOption(optionValue, element);
28620 selectCtrl.ngModelCtrl.$render();
28621 chromeHack(element);
28622 }
28623
28624 // Only update trigger option updates if this is an option within a `select`
28625 // that also has `ngModel` attached
28626 if (selectCtrl && selectCtrl.ngModelCtrl) {
28627
28628 if (valueInterpolated) {
28629 // The value attribute is interpolated
28630 var oldVal;
28631 attr.$observe('value', function valueAttributeObserveAction(newVal) {
28632 if (isDefined(oldVal)) {
28633 selectCtrl.removeOption(oldVal);
28634 }
28635 oldVal = newVal;
28636 addOption(newVal);
28637 });
28638 } else if (interpolateFn) {
28639 // The text content is interpolated
28640 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
28641 attr.$set('value', newVal);
28642 if (oldVal !== newVal) {
28643 selectCtrl.removeOption(oldVal);
28644 }
28645 addOption(newVal);
28646 });
28647 } else {
28648 // The value attribute is static
28649 addOption(attr.value);
28650 }
28651
28652 element.on('$destroy', function() {
28653 selectCtrl.removeOption(attr.value);
28654 selectCtrl.ngModelCtrl.$render();
28655 });
28656 }
28657 };
28658 }
28659 };
28660}];
28661
28662var styleDirective = valueFn({
28663 restrict: 'E',
28664 terminal: false
28665});
28666
28667var requiredDirective = function() {
28668 return {
28669 restrict: 'A',
28670 require: '?ngModel',
28671 link: function(scope, elm, attr, ctrl) {
28672 if (!ctrl) return;
28673 attr.required = true; // force truthy in case we are on non input element
28674
28675 ctrl.$validators.required = function(modelValue, viewValue) {
28676 return !attr.required || !ctrl.$isEmpty(viewValue);
28677 };
28678
28679 attr.$observe('required', function() {
28680 ctrl.$validate();
28681 });
28682 }
28683 };
28684};
28685
28686
28687var patternDirective = function() {
28688 return {
28689 restrict: 'A',
28690 require: '?ngModel',
28691 link: function(scope, elm, attr, ctrl) {
28692 if (!ctrl) return;
28693
28694 var regexp, patternExp = attr.ngPattern || attr.pattern;
28695 attr.$observe('pattern', function(regex) {
28696 if (isString(regex) && regex.length > 0) {
28697 regex = new RegExp('^' + regex + '$');
28698 }
28699
28700 if (regex && !regex.test) {
28701 throw minErr('ngPattern')('noregexp',
28702 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28703 regex, startingTag(elm));
28704 }
28705
28706 regexp = regex || undefined;
28707 ctrl.$validate();
28708 });
28709
28710 ctrl.$validators.pattern = function(modelValue, viewValue) {
28711 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28712 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28713 };
28714 }
28715 };
28716};
28717
28718
28719var maxlengthDirective = function() {
28720 return {
28721 restrict: 'A',
28722 require: '?ngModel',
28723 link: function(scope, elm, attr, ctrl) {
28724 if (!ctrl) return;
28725
28726 var maxlength = -1;
28727 attr.$observe('maxlength', function(value) {
28728 var intVal = toInt(value);
28729 maxlength = isNaN(intVal) ? -1 : intVal;
28730 ctrl.$validate();
28731 });
28732 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28733 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28734 };
28735 }
28736 };
28737};
28738
28739var minlengthDirective = function() {
28740 return {
28741 restrict: 'A',
28742 require: '?ngModel',
28743 link: function(scope, elm, attr, ctrl) {
28744 if (!ctrl) return;
28745
28746 var minlength = 0;
28747 attr.$observe('minlength', function(value) {
28748 minlength = toInt(value) || 0;
28749 ctrl.$validate();
28750 });
28751 ctrl.$validators.minlength = function(modelValue, viewValue) {
28752 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28753 };
28754 }
28755 };
28756};
28757
28758if (window.angular.bootstrap) {
28759 //AngularJS is already loaded, so we can return here...
28760 console.log('WARNING: Tried to load angular more than once.');
28761 return;
28762}
28763
28764//try to bind to jquery now so that one can write jqLite(document).ready()
28765//but we will rebind on bootstrap again.
28766bindJQuery();
28767
28768publishExternalAPI(angular);
28769
28770angular.module("ngLocale", [], ["$provide", function($provide) {
28771var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28772function getDecimals(n) {
28773 n = n + '';
28774 var i = n.indexOf('.');
28775 return (i == -1) ? 0 : n.length - i - 1;
28776}
28777
28778function getVF(n, opt_precision) {
28779 var v = opt_precision;
28780
28781 if (undefined === v) {
28782 v = Math.min(getDecimals(n), 3);
28783 }
28784
28785 var base = Math.pow(10, v);
28786 var f = ((n * base) | 0) % base;
28787 return {v: v, f: f};
28788}
28789
28790$provide.value("$locale", {
28791 "DATETIME_FORMATS": {
28792 "AMPMS": [
28793 "AM",
28794 "PM"
28795 ],
28796 "DAY": [
28797 "Sunday",
28798 "Monday",
28799 "Tuesday",
28800 "Wednesday",
28801 "Thursday",
28802 "Friday",
28803 "Saturday"
28804 ],
28805 "ERANAMES": [
28806 "Before Christ",
28807 "Anno Domini"
28808 ],
28809 "ERAS": [
28810 "BC",
28811 "AD"
28812 ],
28813 "FIRSTDAYOFWEEK": 6,
28814 "MONTH": [
28815 "January",
28816 "February",
28817 "March",
28818 "April",
28819 "May",
28820 "June",
28821 "July",
28822 "August",
28823 "September",
28824 "October",
28825 "November",
28826 "December"
28827 ],
28828 "SHORTDAY": [
28829 "Sun",
28830 "Mon",
28831 "Tue",
28832 "Wed",
28833 "Thu",
28834 "Fri",
28835 "Sat"
28836 ],
28837 "SHORTMONTH": [
28838 "Jan",
28839 "Feb",
28840 "Mar",
28841 "Apr",
28842 "May",
28843 "Jun",
28844 "Jul",
28845 "Aug",
28846 "Sep",
28847 "Oct",
28848 "Nov",
28849 "Dec"
28850 ],
28851 "WEEKENDRANGE": [
28852 5,
28853 6
28854 ],
28855 "fullDate": "EEEE, MMMM d, y",
28856 "longDate": "MMMM d, y",
28857 "medium": "MMM d, y h:mm:ss a",
28858 "mediumDate": "MMM d, y",
28859 "mediumTime": "h:mm:ss a",
28860 "short": "M/d/yy h:mm a",
28861 "shortDate": "M/d/yy",
28862 "shortTime": "h:mm a"
28863 },
28864 "NUMBER_FORMATS": {
28865 "CURRENCY_SYM": "$",
28866 "DECIMAL_SEP": ".",
28867 "GROUP_SEP": ",",
28868 "PATTERNS": [
28869 {
28870 "gSize": 3,
28871 "lgSize": 3,
28872 "maxFrac": 3,
28873 "minFrac": 0,
28874 "minInt": 1,
28875 "negPre": "-",
28876 "negSuf": "",
28877 "posPre": "",
28878 "posSuf": ""
28879 },
28880 {
28881 "gSize": 3,
28882 "lgSize": 3,
28883 "maxFrac": 2,
28884 "minFrac": 2,
28885 "minInt": 1,
28886 "negPre": "-\u00a4",
28887 "negSuf": "",
28888 "posPre": "\u00a4",
28889 "posSuf": ""
28890 }
28891 ]
28892 },
28893 "id": "en-us",
28894 "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;}
28895});
28896}]);
28897
28898 jqLite(document).ready(function() {
28899 angularInit(document, bootstrap);
28900 });
28901
28902})(window, document);
28903
28904!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>');