2 * @license AngularJS v1.0.7
3 * (c) 2010-2012 Google, Inc. http://angularjs.org
6 (function(window
, document
, undefined) {
9 ////////////////////////////////////
13 * @name angular.lowercase
16 * @description Converts the specified string to lowercase.
17 * @param {string} string String to be converted to lowercase.
18 * @returns {string} Lowercased string.
20 var lowercase = function(string
){return isString(string
) ? string
.toLowerCase() : string
;};
25 * @name angular.uppercase
28 * @description Converts the specified string to uppercase.
29 * @param {string} string String to be converted to uppercase.
30 * @returns {string} Uppercased string.
32 var uppercase = function(string
){return isString(string
) ? string
.toUpperCase() : string
;};
35 var manualLowercase = function(s
) {
37 ? s
.replace(/[A-Z]/g, function(ch
) {return String
.fromCharCode(ch
.charCodeAt(0) | 32);})
40 var manualUppercase = function(s
) {
42 ? s
.replace(/[a-z]/g, function(ch
) {return String
.fromCharCode(ch
.charCodeAt(0) & ~32);})
47 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
48 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
49 // with correct but slower alternatives.
50 if ('i' !== 'I'.toLowerCase()) {
51 lowercase
= manualLowercase
;
52 uppercase
= manualUppercase
;
56 var /** holds major version number for IE or NaN for real browsers */
57 msie
= int((/msie (\d
+)/.exec(lowercase(navigator
.userAgent
)) || [])[1]),
58 jqLite
, // delay binding since jQuery could be loaded after us.
59 jQuery
, // delay binding
62 toString
= Object
.prototype.toString
,
65 angular
= window
.angular
|| (window
.angular
= {}),
68 uid
= ['0', '0', '0'];
74 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
76 function isArrayLike(obj
) {
77 if (!obj
|| (typeof obj
.length
!== 'number')) return false;
79 // We have on object which has length property. Should we treat it as array?
80 if (typeof obj
.hasOwnProperty
!= 'function' &&
81 typeof obj
.constructor != 'function') {
82 // This is here for IE8: it is a bogus object treat it as array;
85 return obj
instanceof JQLite
|| // JQLite
86 (jQuery
&& obj
instanceof jQuery
) || // jQuery
87 toString
.call(obj
) !== '[object Object]' || // some browser native object
88 typeof obj
.callee
=== 'function'; // arguments (on IE8 looks like regular obj)
95 * @name angular.forEach
99 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
100 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
101 * is the value of an object property or an array element and `key` is the object property key or
102 * array element index. Specifying a `context` for the function is optional.
104 * Note: this function was previously known as `angular.foreach`.
107 var values = {name: 'misko', gender: 'male'};
109 angular.forEach(values, function(value, key){
110 this.push(key + ': ' + value);
112 expect(log).toEqual(['name: misko', 'gender:male']);
115 * @param {Object|Array} obj Object to iterate over.
116 * @param {Function} iterator Iterator function.
117 * @param {Object=} context Object to become context (`this`) for the iterator function.
118 * @returns {Object|Array} Reference to `obj`.
120 function forEach(obj
, iterator
, context
) {
123 if (isFunction(obj
)){
125 if (key
!= 'prototype' && key
!= 'length' && key
!= 'name' && obj
.hasOwnProperty(key
)) {
126 iterator
.call(context
, obj
[key
], key
);
129 } else if (obj
.forEach
&& obj
.forEach
!== forEach
) {
130 obj
.forEach(iterator
, context
);
131 } else if (isArrayLike(obj
)) {
132 for (key
= 0; key
< obj
.length
; key
++)
133 iterator
.call(context
, obj
[key
], key
);
136 if (obj
.hasOwnProperty(key
)) {
137 iterator
.call(context
, obj
[key
], key
);
145 function sortedKeys(obj
) {
147 for (var key
in obj
) {
148 if (obj
.hasOwnProperty(key
)) {
155 function forEachSorted(obj
, iterator
, context
) {
156 var keys
= sortedKeys(obj
);
157 for ( var i
= 0; i
< keys
.length
; i
++) {
158 iterator
.call(context
, obj
[keys
[i
]], keys
[i
]);
165 * when using forEach the params are value, key, but it is often useful to have key, value.
166 * @param {function(string, *)} iteratorFn
167 * @returns {function(*, string)}
169 function reverseParams(iteratorFn
) {
170 return function(value
, key
) { iteratorFn(key
, value
) };
174 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
175 * characters such as '012ABC'. The reason why we are not using simply a number counter is that
176 * the number string gets longer over time, and it can also overflow, where as the nextId
177 * will grow much slower, it is a string, and it will never overflow.
179 * @returns an unique alpha-numeric string
182 var index
= uid
.length
;
187 digit
= uid
[index
].charCodeAt(0);
188 if (digit
== 57 /*'9'*/) {
192 if (digit
== 90 /*'Z'*/) {
195 uid
[index
] = String
.fromCharCode(digit
+ 1);
205 * Set or clear the hashkey for an object.
207 * @param h the hashkey (!truthy to delete the hashkey)
209 function setHashKey(obj
, h
) {
214 delete obj
.$$hashKey
;
220 * @name angular.extend
224 * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
225 * to `dst`. You can specify multiple `src` objects.
227 * @param {Object} dst Destination object.
228 * @param {...Object} src Source object(s).
229 * @returns {Object} Reference to `dst`.
231 function extend(dst
) {
232 var h
= dst
.$$hashKey
;
233 forEach(arguments
, function(obj
){
235 forEach(obj
, function(value
, key
){
246 return parseInt(str
, 10);
250 function inherit(parent
, extra
) {
251 return extend(new (extend(function() {}, {prototype:parent
}))(), extra
);
261 * A function that performs no operations. This function can be useful when writing code in the
264 function foo(callback) {
265 var result = calculateResult();
266 (callback || angular.noop)(result);
276 * @name angular.identity
280 * A function that returns its first argument. This function is useful when writing code in the
284 function transformer(transformationFn, value) {
285 return (transformationFn || identity)(value);
289 function identity($) {return $;}
290 identity
.$inject
= [];
293 function valueFn(value
) {return function() {return value
;};}
297 * @name angular.isUndefined
301 * Determines if a reference is undefined.
303 * @param {*} value Reference to check.
304 * @returns {boolean} True if `value` is undefined.
306 function isUndefined(value
){return typeof value
== 'undefined';}
311 * @name angular.isDefined
315 * Determines if a reference is defined.
317 * @param {*} value Reference to check.
318 * @returns {boolean} True if `value` is defined.
320 function isDefined(value
){return typeof value
!= 'undefined';}
325 * @name angular.isObject
329 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
330 * considered to be objects.
332 * @param {*} value Reference to check.
333 * @returns {boolean} True if `value` is an `Object` but not `null`.
335 function isObject(value
){return value
!= null && typeof value
== 'object';}
340 * @name angular.isString
344 * Determines if a reference is a `String`.
346 * @param {*} value Reference to check.
347 * @returns {boolean} True if `value` is a `String`.
349 function isString(value
){return typeof value
== 'string';}
354 * @name angular.isNumber
358 * Determines if a reference is a `Number`.
360 * @param {*} value Reference to check.
361 * @returns {boolean} True if `value` is a `Number`.
363 function isNumber(value
){return typeof value
== 'number';}
368 * @name angular.isDate
372 * Determines if a value is a date.
374 * @param {*} value Reference to check.
375 * @returns {boolean} True if `value` is a `Date`.
377 function isDate(value
){
378 return toString
.apply(value
) == '[object Date]';
384 * @name angular.isArray
388 * Determines if a reference is an `Array`.
390 * @param {*} value Reference to check.
391 * @returns {boolean} True if `value` is an `Array`.
393 function isArray(value
) {
394 return toString
.apply(value
) == '[object Array]';
400 * @name angular.isFunction
404 * Determines if a reference is a `Function`.
406 * @param {*} value Reference to check.
407 * @returns {boolean} True if `value` is a `Function`.
409 function isFunction(value
){return typeof value
== 'function';}
413 * Checks if `obj` is a window object.
416 * @param {*} obj Object to check
417 * @returns {boolean} True if `obj` is a window obj.
419 function isWindow(obj
) {
420 return obj
&& obj
.document
&& obj
.location
&& obj
.alert
&& obj
.setInterval
;
424 function isScope(obj
) {
425 return obj
&& obj
.$evalAsync
&& obj
.$watch
;
429 function isFile(obj
) {
430 return toString
.apply(obj
) === '[object File]';
434 function isBoolean(value
) {
435 return typeof value
== 'boolean';
439 function trim(value
) {
440 return isString(value
) ? value
.replace(/^\s*/, '').replace(/\s*$/, '') : value
;
445 * @name angular.isElement
449 * Determines if a reference is a DOM element (or wrapped jQuery element).
451 * @param {*} value Reference to check.
452 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
454 function isElement(node
) {
456 (node
.nodeName
// we are a direct element
457 || (node
.bind
&& node
.find
)); // we have a bind and find method part of jQuery API
461 * @param str 'key1,key2,...'
462 * @returns {object} in the form of {key1:true, key2:true, ...}
464 function makeMap(str
){
465 var obj
= {}, items
= str
.split(","), i
;
466 for ( i
= 0; i
< items
.length
; i
++ )
467 obj
[ items
[i
] ] = true;
473 nodeName_ = function(element
) {
474 element
= element
.nodeName
? element : element
[0];
475 return (element
.scopeName
&& element
.scopeName
!= 'HTML')
476 ? uppercase(element
.scopeName
+ ':' + element
.nodeName
) : element
.nodeName
;
479 nodeName_ = function(element
) {
480 return element
.nodeName
? element
.nodeName : element
[0].nodeName
;
485 function map(obj
, iterator
, context
) {
487 forEach(obj
, function(value
, index
, list
) {
488 results
.push(iterator
.call(context
, value
, index
, list
));
496 * Determines the number of elements in an array, the number of properties an object has, or
497 * the length of a string.
499 * Note: This function is used to augment the Object type in Angular expressions. See
500 * {@link angular.Object} for more information about Angular arrays.
502 * @param {Object|Array|string} obj Object, array, or string to inspect.
503 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
504 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
506 function size(obj
, ownPropsOnly
) {
509 if (isArray(obj
) || isString(obj
)) {
511 } else if (isObject(obj
)){
513 if (!ownPropsOnly
|| obj
.hasOwnProperty(key
))
521 function includes(array
, obj
) {
522 return indexOf(array
, obj
) != -1;
525 function indexOf(array
, obj
) {
526 if (array
.indexOf
) return array
.indexOf(obj
);
528 for ( var i
= 0; i
< array
.length
; i
++) {
529 if (obj
=== array
[i
]) return i
;
534 function arrayRemove(array
, value
) {
535 var index
= indexOf(array
, value
);
537 array
.splice(index
, 1);
541 function isLeafNode (node
) {
543 switch (node
.nodeName
) {
559 * Creates a deep copy of `source`, which should be an object or an array.
561 * * If no destination is supplied, a copy of the object or array is created.
562 * * If a destination is provided, all of its elements (for array) or properties (for objects)
563 * are deleted and then all elements/properties from the source are copied to it.
564 * * If `source` is not an object or array, `source` is returned.
566 * Note: this function is used to augment the Object type in Angular expressions. See
567 * {@link ng.$filter} for more information about Angular arrays.
569 * @param {*} source The source that will be used to make a copy.
570 * Can be any type, including primitives, `null`, and `undefined`.
571 * @param {(Object|Array)=} destination Destination into which the source is copied. If
572 * provided, must be of the same type as `source`.
573 * @returns {*} The copy or updated `destination`, if `destination` was specified.
575 function copy(source
, destination
){
576 if (isWindow(source
) || isScope(source
)) throw Error("Can't copy Window or Scope");
578 destination
= source
;
580 if (isArray(source
)) {
581 destination
= copy(source
, []);
582 } else if (isDate(source
)) {
583 destination
= new Date(source
.getTime());
584 } else if (isObject(source
)) {
585 destination
= copy(source
, {});
589 if (source
=== destination
) throw Error("Can't copy equivalent objects or arrays");
590 if (isArray(source
)) {
591 destination
.length
= 0;
592 for ( var i
= 0; i
< source
.length
; i
++) {
593 destination
.push(copy(source
[i
]));
596 var h
= destination
.$$hashKey
;
597 forEach(destination
, function(value
, key
){
598 delete destination
[key
];
600 for ( var key
in source
) {
601 destination
[key
] = copy(source
[key
]);
603 setHashKey(destination
,h
);
610 * Create a shallow copy of an object
612 function shallowCopy(src
, dst
) {
615 for(var key
in src
) {
616 if (src
.hasOwnProperty(key
) && key
.substr(0, 2) !== '$$') {
627 * @name angular.equals
631 * Determines if two objects or two values are equivalent. Supports value types, arrays and
634 * Two objects or values are considered equivalent if at least one of the following is true:
636 * * Both objects or values pass `===` comparison.
637 * * Both objects or values are of the same type and all of their properties pass `===` comparison.
638 * * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal)
640 * During a property comparision, properties of `function` type and properties with names
641 * that begin with `$` are ignored.
643 * Scope and DOMWindow objects are being compared only by identify (`===`).
645 * @param {*} o1 Object or value to compare.
646 * @param {*} o2 Object or value to compare.
647 * @returns {boolean} True if arguments are equal.
649 function equals(o1
, o2
) {
650 if (o1
=== o2
) return true;
651 if (o1
=== null || o2
=== null) return false;
652 if (o1
!== o1
&& o2
!== o2
) return true; // NaN === NaN
653 var t1
= typeof o1
, t2
= typeof o2
, length
, key
, keySet
;
655 if (t1
== 'object') {
657 if ((length
= o1
.length
) == o2
.length
) {
658 for(key
=0; key
<length
; key
++) {
659 if (!equals(o1
[key
], o2
[key
])) return false;
663 } else if (isDate(o1
)) {
664 return isDate(o2
) && o1
.getTime() == o2
.getTime();
666 if (isScope(o1
) || isScope(o2
) || isWindow(o1
) || isWindow(o2
)) return false;
669 if (key
.charAt(0) === '$' || isFunction(o1
[key
])) continue;
670 if (!equals(o1
[key
], o2
[key
])) return false;
675 key
.charAt(0) !== '$' &&
676 o2
[key
] !== undefined &&
677 !isFunction(o2
[key
])) return false;
687 function concat(array1
, array2
, index
) {
688 return array1
.concat(slice
.call(array2
, index
));
691 function sliceArgs(args
, startIndex
) {
692 return slice
.call(args
, startIndex
|| 0);
702 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
703 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
704 * known as [function currying](http://en.wikipedia.org/wiki/Currying).
706 * @param {Object} self Context which `fn` should be evaluated in.
707 * @param {function()} fn Function to be bound.
708 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
709 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
711 function bind(self
, fn
) {
712 var curryArgs
= arguments
.length
> 2 ? sliceArgs(arguments
, 2) : [];
713 if (isFunction(fn
) && !(fn
instanceof RegExp
)) {
714 return curryArgs
.length
716 return arguments
.length
717 ? fn
.apply(self
, curryArgs
.concat(slice
.call(arguments
, 0)))
718 : fn
.apply(self
, curryArgs
);
721 return arguments
.length
722 ? fn
.apply(self
, arguments
)
726 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
732 function toJsonReplacer(key
, value
) {
735 if (/^\$+/.test(key
)) {
737 } else if (isWindow(value
)) {
739 } else if (value
&& document
=== value
) {
741 } else if (isScope(value
)) {
751 * @name angular.toJson
755 * Serializes input into a JSON-formatted string.
757 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
758 * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
759 * @returns {string} Jsonified string representing `obj`.
761 function toJson(obj
, pretty
) {
762 return JSON
.stringify(obj
, toJsonReplacer
, pretty
? ' ' : null);
768 * @name angular.fromJson
772 * Deserializes a JSON string.
774 * @param {string} json JSON string to deserialize.
775 * @returns {Object|Array|Date|string|number} Deserialized thingy.
777 function fromJson(json
) {
778 return isString(json
)
784 function toBoolean(value
) {
785 if (value
&& value
.length
!== 0) {
786 var v
= lowercase("" + value
);
787 value
= !(v
== 'f' || v
== '0' || v
== 'false' || v
== 'no' || v
== 'n' || v
== '[]');
795 * @returns {string} Returns the string representation of the element.
797 function startingTag(element
) {
798 element
= jqLite(element
).clone();
800 // turns out IE does not let you set .html() on elements which
801 // are not allowed to have children. So we just ignore it.
804 // As Per DOM Standards
806 var elemHtml
= jqLite('<div>').append(element
).html();
808 return element
[0].nodeType
=== TEXT_NODE
? lowercase(elemHtml
) :
810 match(/^(<[^>]+>)/)[1].
811 replace(/^<([\w\-]+)/, function(match
, nodeName
) { return '<' + lowercase(nodeName
); });
813 return lowercase(elemHtml
);
819 /////////////////////////////////////////////////
822 * Parses an escaped url query string into key-value pairs.
823 * @returns Object.<(string|boolean)>
825 function parseKeyValue(/**string*/keyValue
) {
826 var obj
= {}, key_value
, key
;
827 forEach((keyValue
|| "").split('&'), function(keyValue
){
829 key_value
= keyValue
.split('=');
830 key
= decodeURIComponent(key_value
[0]);
831 obj
[key
] = isDefined(key_value
[1]) ? decodeURIComponent(key_value
[1]) : true;
837 function toKeyValue(obj
) {
839 forEach(obj
, function(value
, key
) {
840 parts
.push(encodeUriQuery(key
, true) + (value
=== true ? '' : '=' + encodeUriQuery(value
, true)));
842 return parts
.length
? parts
.join('&') : '';
847 * We need our custom method because encodeURIComponent is too agressive and doesn't follow
848 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
851 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
852 * pct-encoded = "%" HEXDIG HEXDIG
853 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
854 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
855 * / "*" / "+" / "," / ";" / "="
857 function encodeUriSegment(val
) {
858 return encodeUriQuery(val
, true).
859 replace(/%26/gi, '&').
860 replace(/%3D/gi, '=').
861 replace(/%2B/gi, '+');
866 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
867 * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
868 * encoded per http://tools.ietf.org/html/rfc3986:
869 * query = *( pchar / "/" / "?" )
870 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
871 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
872 * pct-encoded = "%" HEXDIG HEXDIG
873 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
874 * / "*" / "+" / "," / ";" / "="
876 function encodeUriQuery(val
, pctEncodeSpaces
) {
877 return encodeURIComponent(val
).
878 replace(/%40/gi, '@').
879 replace(/%3A/gi, ':').
880 replace(/%24/g, '$').
881 replace(/%2C/gi, ',').
882 replace(/%20/g, (pctEncodeSpaces
? '%20' : '+'));
888 * @name ng.directive:ngApp
891 * @param {angular.Module} ngApp an optional application
892 * {@link angular.module module} name to load.
896 * Use this directive to auto-bootstrap an application. Only
897 * one directive can be used per HTML document. The directive
898 * designates the root of the application and is typically placed
899 * at the root of the page.
901 * In the example below if the `ngApp` directive would not be placed
902 * on the `html` element then the document would not be compiled
903 * and the `{{ 1+2 }}` would not be resolved to `3`.
905 * `ngApp` is the easiest way to bootstrap an application.
909 I can add: 1 + 2 = {{ 1+2 }}
914 function angularInit(element
, bootstrap
) {
915 var elements
= [element
],
918 names
= ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
919 NG_APP_CLASS_REGEXP
= /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
921 function append(element
) {
922 element
&& elements
.push(element
);
925 forEach(names
, function(name
) {
927 append(document
.getElementById(name
));
928 name
= name
.replace(':', '\\:');
929 if (element
.querySelectorAll
) {
930 forEach(element
.querySelectorAll('.' + name
), append
);
931 forEach(element
.querySelectorAll('.' + name
+ '\\:'), append
);
932 forEach(element
.querySelectorAll('[' + name
+ ']'), append
);
936 forEach(elements
, function(element
) {
938 var className
= ' ' + element
.className
+ ' ';
939 var match
= NG_APP_CLASS_REGEXP
.exec(className
);
941 appElement
= element
;
942 module
= (match
[2] || '').replace(/\s+/g, ',');
944 forEach(element
.attributes
, function(attr
) {
945 if (!appElement
&& names
[attr
.name
]) {
946 appElement
= element
;
954 bootstrap(appElement
, module
? [module
] : []);
960 * @name angular.bootstrap
962 * Use this function to manually start up angular application.
964 * See: {@link guide/bootstrap Bootstrap}
966 * @param {Element} element DOM element which is the root of angular application.
967 * @param {Array<String|Function>=} modules an array of module declarations. See: {@link angular.module modules}
968 * @returns {AUTO.$injector} Returns the newly created injector for this app.
970 function bootstrap(element
, modules
) {
971 var resumeBootstrapInternal = function() {
972 element
= jqLite(element
);
973 modules
= modules
|| [];
974 modules
.unshift(['$provide', function($provide
) {
975 $provide
.value('$rootElement', element
);
977 modules
.unshift('ng');
978 var injector
= createInjector(modules
);
979 injector
.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
980 function(scope
, element
, compile
, injector
) {
981 scope
.$apply(function() {
982 element
.data('$injector', injector
);
983 compile(element
)(scope
);
990 var NG_DEFER_BOOTSTRAP
= /^NG_DEFER_BOOTSTRAP!/;
992 if (window
&& !NG_DEFER_BOOTSTRAP
.test(window
.name
)) {
993 return resumeBootstrapInternal();
996 window
.name
= window
.name
.replace(NG_DEFER_BOOTSTRAP
, '');
997 angular
.resumeBootstrap = function(extraModules
) {
998 forEach(extraModules
, function(module
) {
999 modules
.push(module
);
1001 resumeBootstrapInternal();
1005 var SNAKE_CASE_REGEXP
= /[A-Z]/g;
1006 function snake_case(name
, separator
){
1007 separator
= separator
|| '_';
1008 return name
.replace(SNAKE_CASE_REGEXP
, function(letter
, pos
) {
1009 return (pos
? separator : '') + letter
.toLowerCase();
1013 function bindJQuery() {
1014 // bind to jQuery if present;
1015 jQuery
= window
.jQuery
;
1016 // reset to jQuery or default to us.
1020 scope: JQLitePrototype
.scope
,
1021 controller: JQLitePrototype
.controller
,
1022 injector: JQLitePrototype
.injector
,
1023 inheritedData: JQLitePrototype
.inheritedData
1025 JQLitePatchJQueryRemove('remove', true);
1026 JQLitePatchJQueryRemove('empty');
1027 JQLitePatchJQueryRemove('html');
1031 angular
.element
= jqLite
;
1035 * throw error if the argument is falsy.
1037 function assertArg(arg
, name
, reason
) {
1039 throw new Error("Argument '" + (name
|| '?') + "' is " + (reason
|| "required"));
1044 function assertArgFn(arg
, name
, acceptArrayAnnotation
) {
1045 if (acceptArrayAnnotation
&& isArray(arg
)) {
1046 arg
= arg
[arg
.length
- 1];
1049 assertArg(isFunction(arg
), name
, 'not a function, got ' +
1050 (arg
&& typeof arg
== 'object' ? arg
.constructor.name
|| 'Object' : typeof arg
));
1056 * @name angular.Module
1059 * Interface for configuring angular {@link angular.module modules}.
1062 function setupModuleLoader(window
) {
1064 function ensure(obj
, name
, factory
) {
1065 return obj
[name
] || (obj
[name
] = factory());
1068 return ensure(ensure(window
, 'angular', Object
), 'module', function() {
1069 /** @type {Object.<string, angular.Module>} */
1074 * @name angular.module
1077 * The `angular.module` is a global place for creating and registering Angular modules. All
1078 * modules (angular core or 3rd party) that should be available to an application must be
1079 * registered using this mechanism.
1084 * A module is a collocation of services, directives, filters, and configuration information. Module
1085 * is used to configure the {@link AUTO.$injector $injector}.
1088 * // Create a new module
1089 * var myModule = angular.module('myModule', []);
1091 * // register a new service
1092 * myModule.value('appName', 'MyCoolApp');
1094 * // configure existing services inside initialization blocks.
1095 * myModule.config(function($locationProvider) {
1096 * // Configure existing providers
1097 * $locationProvider.hashPrefix('!');
1101 * Then you can create an injector and load your modules like this:
1104 * var injector = angular.injector(['ng', 'MyModule'])
1107 * However it's more likely that you'll just use
1108 * {@link ng.directive:ngApp ngApp} or
1109 * {@link angular.bootstrap} to simplify this process for you.
1111 * @param {!string} name The name of the module to create or retrieve.
1112 * @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the
1113 * the module is being retrieved for further configuration.
1114 * @param {Function} configFn Optional configuration function for the module. Same as
1115 * {@link angular.Module#config Module#config()}.
1116 * @returns {module} new module with the {@link angular.Module} api.
1118 return function module(name
, requires
, configFn
) {
1119 if (requires
&& modules
.hasOwnProperty(name
)) {
1120 modules
[name
] = null;
1122 return ensure(modules
, name
, function() {
1124 throw Error('No module: ' + name
);
1127 /** @type {!Array.<Array.<*>>} */
1128 var invokeQueue
= [];
1130 /** @type {!Array.<Function>} */
1133 var config
= invokeLater('$injector', 'invoke');
1135 /** @type {angular.Module} */
1136 var moduleInstance
= {
1138 _invokeQueue: invokeQueue
,
1139 _runBlocks: runBlocks
,
1143 * @name angular.Module#requires
1144 * @propertyOf angular.Module
1145 * @returns {Array.<string>} List of module names which must be loaded before this module.
1147 * Holds the list of modules which the injector will load before the current module is loaded.
1153 * @name angular.Module#name
1154 * @propertyOf angular.Module
1155 * @returns {string} Name of the module.
1163 * @name angular.Module#provider
1164 * @methodOf angular.Module
1165 * @param {string} name service name
1166 * @param {Function} providerType Construction function for creating new instance of the service.
1168 * See {@link AUTO.$provide#provider $provide.provider()}.
1170 provider: invokeLater('$provide', 'provider'),
1174 * @name angular.Module#factory
1175 * @methodOf angular.Module
1176 * @param {string} name service name
1177 * @param {Function} providerFunction Function for creating new instance of the service.
1179 * See {@link AUTO.$provide#factory $provide.factory()}.
1181 factory: invokeLater('$provide', 'factory'),
1185 * @name angular.Module#service
1186 * @methodOf angular.Module
1187 * @param {string} name service name
1188 * @param {Function} constructor A constructor function that will be instantiated.
1190 * See {@link AUTO.$provide#service $provide.service()}.
1192 service: invokeLater('$provide', 'service'),
1196 * @name angular.Module#value
1197 * @methodOf angular.Module
1198 * @param {string} name service name
1199 * @param {*} object Service instance object.
1201 * See {@link AUTO.$provide#value $provide.value()}.
1203 value: invokeLater('$provide', 'value'),
1207 * @name angular.Module#constant
1208 * @methodOf angular.Module
1209 * @param {string} name constant name
1210 * @param {*} object Constant value.
1212 * Because the constant are fixed, they get applied before other provide methods.
1213 * See {@link AUTO.$provide#constant $provide.constant()}.
1215 constant: invokeLater('$provide', 'constant', 'unshift'),
1219 * @name angular.Module#filter
1220 * @methodOf angular.Module
1221 * @param {string} name Filter name.
1222 * @param {Function} filterFactory Factory function for creating new instance of filter.
1224 * See {@link ng.$filterProvider#register $filterProvider.register()}.
1226 filter: invokeLater('$filterProvider', 'register'),
1230 * @name angular.Module#controller
1231 * @methodOf angular.Module
1232 * @param {string} name Controller name.
1233 * @param {Function} constructor Controller constructor function.
1235 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
1237 controller: invokeLater('$controllerProvider', 'register'),
1241 * @name angular.Module#directive
1242 * @methodOf angular.Module
1243 * @param {string} name directive name
1244 * @param {Function} directiveFactory Factory function for creating new instance of
1247 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
1249 directive: invokeLater('$compileProvider', 'directive'),
1253 * @name angular.Module#config
1254 * @methodOf angular.Module
1255 * @param {Function} configFn Execute this function on module load. Useful for service
1258 * Use this method to register work which needs to be performed on module loading.
1264 * @name angular.Module#run
1265 * @methodOf angular.Module
1266 * @param {Function} initializationFn Execute this function after injector creation.
1267 * Useful for application initialization.
1269 * Use this method to register work which should be performed when the injector is done
1270 * loading all modules.
1272 run: function(block
) {
1273 runBlocks
.push(block
);
1282 return moduleInstance
;
1285 * @param {string} provider
1286 * @param {string} method
1287 * @param {String=} insertMethod
1288 * @returns {angular.Module}
1290 function invokeLater(provider
, method
, insertMethod
) {
1292 invokeQueue
[insertMethod
|| 'push']([provider
, method
, arguments
]);
1293 return moduleInstance
;
1304 * @name angular.version
1306 * An object that contains information about the current AngularJS version. This object has the
1307 * following properties:
1309 * - `full` – `{string}` – Full version string, such as "0.9.18".
1310 * - `major` – `{number}` – Major version number, such as "0".
1311 * - `minor` – `{number}` – Minor version number, such as "9".
1312 * - `dot` – `{number}` – Dot version number, such as "18".
1313 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
1316 full: '1.0.7', // all of these placeholder strings will be replaced by grunt's
1317 major: 1, // package task
1320 codeName: 'monochromatic-rainbow'
1324 function publishExternalAPI(angular
){
1326 'bootstrap': bootstrap
,
1332 'injector': createInjector
,
1336 'fromJson': fromJson
,
1337 'identity':identity
,
1338 'isUndefined': isUndefined
,
1339 'isDefined': isDefined
,
1340 'isString': isString
,
1341 'isFunction': isFunction
,
1342 'isObject': isObject
,
1343 'isNumber': isNumber
,
1344 'isElement': isElement
,
1348 'lowercase': lowercase
,
1349 'uppercase': uppercase
,
1350 'callbacks': {counter: 0}
1353 angularModule
= setupModuleLoader(window
);
1355 angularModule('ngLocale');
1357 angularModule('ngLocale', []).provider('$locale', $LocaleProvider
);
1360 angularModule('ng', ['ngLocale'], ['$provide',
1361 function ngModule($provide
) {
1362 $provide
.provider('$compile', $CompileProvider
).
1364 a: htmlAnchorDirective
,
1365 input: inputDirective
,
1366 textarea: inputDirective
,
1367 form: formDirective
,
1368 script: scriptDirective
,
1369 select: selectDirective
,
1370 style: styleDirective
,
1371 option: optionDirective
,
1372 ngBind: ngBindDirective
,
1373 ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective
,
1374 ngBindTemplate: ngBindTemplateDirective
,
1375 ngClass: ngClassDirective
,
1376 ngClassEven: ngClassEvenDirective
,
1377 ngClassOdd: ngClassOddDirective
,
1378 ngCsp: ngCspDirective
,
1379 ngCloak: ngCloakDirective
,
1380 ngController: ngControllerDirective
,
1381 ngForm: ngFormDirective
,
1382 ngHide: ngHideDirective
,
1383 ngInclude: ngIncludeDirective
,
1384 ngInit: ngInitDirective
,
1385 ngNonBindable: ngNonBindableDirective
,
1386 ngPluralize: ngPluralizeDirective
,
1387 ngRepeat: ngRepeatDirective
,
1388 ngShow: ngShowDirective
,
1389 ngSubmit: ngSubmitDirective
,
1390 ngStyle: ngStyleDirective
,
1391 ngSwitch: ngSwitchDirective
,
1392 ngSwitchWhen: ngSwitchWhenDirective
,
1393 ngSwitchDefault: ngSwitchDefaultDirective
,
1394 ngOptions: ngOptionsDirective
,
1395 ngView: ngViewDirective
,
1396 ngTransclude: ngTranscludeDirective
,
1397 ngModel: ngModelDirective
,
1398 ngList: ngListDirective
,
1399 ngChange: ngChangeDirective
,
1400 required: requiredDirective
,
1401 ngRequired: requiredDirective
,
1402 ngValue: ngValueDirective
1404 directive(ngAttributeAliasDirectives
).
1405 directive(ngEventDirectives
);
1407 $anchorScroll: $AnchorScrollProvider
,
1408 $browser: $BrowserProvider
,
1409 $cacheFactory: $CacheFactoryProvider
,
1410 $controller: $ControllerProvider
,
1411 $document: $DocumentProvider
,
1412 $exceptionHandler: $ExceptionHandlerProvider
,
1413 $filter: $FilterProvider
,
1414 $interpolate: $InterpolateProvider
,
1415 $http: $HttpProvider
,
1416 $httpBackend: $HttpBackendProvider
,
1417 $location: $LocationProvider
,
1419 $parse: $ParseProvider
,
1420 $route: $RouteProvider
,
1421 $routeParams: $RouteParamsProvider
,
1422 $rootScope: $RootScopeProvider
,
1424 $sniffer: $SnifferProvider
,
1425 $templateCache: $TemplateCacheProvider
,
1426 $timeout: $TimeoutProvider
,
1427 $window: $WindowProvider
1433 //////////////////////////////////
1435 //////////////////////////////////
1439 * @name angular.element
1443 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
1444 * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
1445 * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
1446 * implementation (commonly referred to as jqLite).
1448 * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
1451 * jqLite is a tiny, API-compatible subset of jQuery that allows
1452 * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
1453 * within a very small footprint, so only a subset of the jQuery API - methods, arguments and
1454 * invocation styles - are supported.
1456 * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
1457 * raw DOM references.
1459 * ## Angular's jQuery lite provides the following methods:
1461 * - [addClass()](http://api.jquery.com/addClass/)
1462 * - [after()](http://api.jquery.com/after/)
1463 * - [append()](http://api.jquery.com/append/)
1464 * - [attr()](http://api.jquery.com/attr/)
1465 * - [bind()](http://api.jquery.com/bind/) - Does not support namespaces
1466 * - [children()](http://api.jquery.com/children/) - Does not support selectors
1467 * - [clone()](http://api.jquery.com/clone/)
1468 * - [contents()](http://api.jquery.com/contents/)
1469 * - [css()](http://api.jquery.com/css/)
1470 * - [data()](http://api.jquery.com/data/)
1471 * - [eq()](http://api.jquery.com/eq/)
1472 * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name
1473 * - [hasClass()](http://api.jquery.com/hasClass/)
1474 * - [html()](http://api.jquery.com/html/)
1475 * - [next()](http://api.jquery.com/next/) - Does not support selectors
1476 * - [parent()](http://api.jquery.com/parent/) - Does not support selectors
1477 * - [prepend()](http://api.jquery.com/prepend/)
1478 * - [prop()](http://api.jquery.com/prop/)
1479 * - [ready()](http://api.jquery.com/ready/)
1480 * - [remove()](http://api.jquery.com/remove/)
1481 * - [removeAttr()](http://api.jquery.com/removeAttr/)
1482 * - [removeClass()](http://api.jquery.com/removeClass/)
1483 * - [removeData()](http://api.jquery.com/removeData/)
1484 * - [replaceWith()](http://api.jquery.com/replaceWith/)
1485 * - [text()](http://api.jquery.com/text/)
1486 * - [toggleClass()](http://api.jquery.com/toggleClass/)
1487 * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers.
1488 * - [unbind()](http://api.jquery.com/unbind/) - Does not support namespaces
1489 * - [val()](http://api.jquery.com/val/)
1490 * - [wrap()](http://api.jquery.com/wrap/)
1492 * ## In addtion to the above, Angular provides additional methods to both jQuery and jQuery lite:
1494 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
1495 * retrieves controller associated with the `ngController` directive. If `name` is provided as
1496 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
1498 * - `injector()` - retrieves the injector of the current element or its parent.
1499 * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current
1500 * element or its parent.
1501 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
1502 * parent element is reached.
1504 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
1505 * @returns {Object} jQuery object.
1508 var jqCache
= JQLite
.cache
= {},
1509 jqName
= JQLite
.expando
= 'ng-' + new Date().getTime(),
1511 addEventListenerFn
= (window
.document
.addEventListener
1512 ? function(element
, type
, fn
) {element
.addEventListener(type
, fn
, false);}
1513 : function(element
, type
, fn
) {element
.attachEvent('on' + type
, fn
);}),
1514 removeEventListenerFn
= (window
.document
.removeEventListener
1515 ? function(element
, type
, fn
) {element
.removeEventListener(type
, fn
, false); }
1516 : function(element
, type
, fn
) {element
.detachEvent('on' + type
, fn
); });
1518 function jqNextId() { return ++jqId
; }
1521 var SPECIAL_CHARS_REGEXP
= /([\:\-\_]+(.))/g;
1522 var MOZ_HACK_REGEXP
= /^moz([A-Z])/;
1525 * Converts snake_case to camelCase.
1526 * Also there is special case for Moz prefix starting with upper case letter.
1527 * @param name Name to normalize
1529 function camelCase(name
) {
1531 replace(SPECIAL_CHARS_REGEXP
, function(_
, separator
, letter
, offset
) {
1532 return offset
? letter
.toUpperCase() : letter
;
1534 replace(MOZ_HACK_REGEXP
, 'Moz$1');
1537 /////////////////////////////////////////////
1538 // jQuery mutation patch
1540 // In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
1541 // $destroy event on all DOM nodes being removed.
1543 /////////////////////////////////////////////
1545 function JQLitePatchJQueryRemove(name
, dispatchThis
) {
1546 var originalJqFn
= jQuery
.fn
[name
];
1547 originalJqFn
= originalJqFn
.$original
|| originalJqFn
;
1548 removePatch
.$original
= originalJqFn
;
1549 jQuery
.fn
[name
] = removePatch
;
1551 function removePatch() {
1553 fireEvent
= dispatchThis
,
1554 set, setIndex
, setLength
,
1555 element
, childIndex
, childLength
, children
,
1558 while(list
.length
) {
1560 for(setIndex
= 0, setLength
= set.length
; setIndex
< setLength
; setIndex
++) {
1561 element
= jqLite(set[setIndex
]);
1563 element
.triggerHandler('$destroy');
1565 fireEvent
= !fireEvent
;
1567 for(childIndex
= 0, childLength
= (children
= element
.children()).length
;
1568 childIndex
< childLength
;
1570 list
.push(jQuery(children
[childIndex
]));
1574 return originalJqFn
.apply(this, arguments
);
1578 /////////////////////////////////////////////
1579 function JQLite(element
) {
1580 if (element
instanceof JQLite
) {
1583 if (!(this instanceof JQLite
)) {
1584 if (isString(element
) && element
.charAt(0) != '<') {
1585 throw Error('selectors not implemented');
1587 return new JQLite(element
);
1590 if (isString(element
)) {
1591 var div
= document
.createElement('div');
1592 // Read about the NoScope elements here:
1593 // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
1594 div
.innerHTML
= '<div> </div>' + element
; // IE insanity to make NoScope elements work!
1595 div
.removeChild(div
.firstChild
); // remove the superfluous div
1596 JQLiteAddNodes(this, div
.childNodes
);
1597 this.remove(); // detach the elements from the temporary DOM div.
1599 JQLiteAddNodes(this, element
);
1603 function JQLiteClone(element
) {
1604 return element
.cloneNode(true);
1607 function JQLiteDealoc(element
){
1608 JQLiteRemoveData(element
);
1609 for ( var i
= 0, children
= element
.childNodes
|| []; i
< children
.length
; i
++) {
1610 JQLiteDealoc(children
[i
]);
1614 function JQLiteUnbind(element
, type
, fn
) {
1615 var events
= JQLiteExpandoStore(element
, 'events'),
1616 handle
= JQLiteExpandoStore(element
, 'handle');
1618 if (!handle
) return; //no listeners registered
1620 if (isUndefined(type
)) {
1621 forEach(events
, function(eventHandler
, type
) {
1622 removeEventListenerFn(element
, type
, eventHandler
);
1623 delete events
[type
];
1626 if (isUndefined(fn
)) {
1627 removeEventListenerFn(element
, type
, events
[type
]);
1628 delete events
[type
];
1630 arrayRemove(events
[type
], fn
);
1635 function JQLiteRemoveData(element
) {
1636 var expandoId
= element
[jqName
],
1637 expandoStore
= jqCache
[expandoId
];
1640 if (expandoStore
.handle
) {
1641 expandoStore
.events
.$destroy
&& expandoStore
.handle({}, '$destroy');
1642 JQLiteUnbind(element
);
1644 delete jqCache
[expandoId
];
1645 element
[jqName
] = undefined; // ie does not allow deletion of attributes on elements.
1649 function JQLiteExpandoStore(element
, key
, value
) {
1650 var expandoId
= element
[jqName
],
1651 expandoStore
= jqCache
[expandoId
|| -1];
1653 if (isDefined(value
)) {
1654 if (!expandoStore
) {
1655 element
[jqName
] = expandoId
= jqNextId();
1656 expandoStore
= jqCache
[expandoId
] = {};
1658 expandoStore
[key
] = value
;
1660 return expandoStore
&& expandoStore
[key
];
1664 function JQLiteData(element
, key
, value
) {
1665 var data
= JQLiteExpandoStore(element
, 'data'),
1666 isSetter
= isDefined(value
),
1667 keyDefined
= !isSetter
&& isDefined(key
),
1668 isSimpleGetter
= keyDefined
&& !isObject(key
);
1670 if (!data
&& !isSimpleGetter
) {
1671 JQLiteExpandoStore(element
, 'data', data
= {});
1678 if (isSimpleGetter
) {
1679 // don't create data in this case.
1680 return data
&& data
[key
];
1690 function JQLiteHasClass(element
, selector
) {
1691 return ((" " + element
.className
+ " ").replace(/[\n\t]/g, " ").
1692 indexOf( " " + selector
+ " " ) > -1);
1695 function JQLiteRemoveClass(element
, cssClasses
) {
1697 forEach(cssClasses
.split(' '), function(cssClass
) {
1698 element
.className
= trim(
1699 (" " + element
.className
+ " ")
1700 .replace(/[\n\t]/g, " ")
1701 .replace(" " + trim(cssClass
) + " ", " ")
1707 function JQLiteAddClass(element
, cssClasses
) {
1709 forEach(cssClasses
.split(' '), function(cssClass
) {
1710 if (!JQLiteHasClass(element
, cssClass
)) {
1711 element
.className
= trim(element
.className
+ ' ' + trim(cssClass
));
1717 function JQLiteAddNodes(root
, elements
) {
1719 elements
= (!elements
.nodeName
&& isDefined(elements
.length
) && !isWindow(elements
))
1722 for(var i
=0; i
< elements
.length
; i
++) {
1723 root
.push(elements
[i
]);
1728 function JQLiteController(element
, name
) {
1729 return JQLiteInheritedData(element
, '$' + (name
|| 'ngController' ) + 'Controller');
1732 function JQLiteInheritedData(element
, name
, value
) {
1733 element
= jqLite(element
);
1735 // if element is the document object work with the html element instead
1736 // this makes $(document).scope() possible
1737 if(element
[0].nodeType
== 9) {
1738 element
= element
.find('html');
1741 while (element
.length
) {
1742 if (value
= element
.data(name
)) return value
;
1743 element
= element
.parent();
1747 //////////////////////////////////////////
1748 // Functions which are declared directly.
1749 //////////////////////////////////////////
1750 var JQLitePrototype
= JQLite
.prototype = {
1751 ready: function(fn
) {
1754 function trigger() {
1760 this.bind('DOMContentLoaded', trigger
); // works for modern browsers and IE9
1761 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
1762 JQLite(window
).bind('load', trigger
); // fallback to window.onload for others
1764 toString: function() {
1766 forEach(this, function(e
){ value
.push('' + e
);});
1767 return '[' + value
.join(', ') + ']';
1770 eq: function(index
) {
1771 return (index
>= 0) ? jqLite(this[index
]) : jqLite(this[this.length
+ index
]);
1780 //////////////////////////////////////////
1781 // Functions iterating getter/setters.
1782 // these functions return self on setter and
1784 //////////////////////////////////////////
1785 var BOOLEAN_ATTR
= {};
1786 forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value
) {
1787 BOOLEAN_ATTR
[lowercase(value
)] = value
;
1789 var BOOLEAN_ELEMENTS
= {};
1790 forEach('input,select,option,textarea,button,form'.split(','), function(value
) {
1791 BOOLEAN_ELEMENTS
[uppercase(value
)] = true;
1794 function getBooleanAttrName(element
, name
) {
1795 // check dom last since we will most likely fail on name
1796 var booleanAttr
= BOOLEAN_ATTR
[name
.toLowerCase()];
1798 // booleanAttr is here twice to minimize DOM access
1799 return booleanAttr
&& BOOLEAN_ELEMENTS
[element
.nodeName
] && booleanAttr
;
1804 inheritedData: JQLiteInheritedData
,
1806 scope: function(element
) {
1807 return JQLiteInheritedData(element
, '$scope');
1810 controller: JQLiteController
,
1812 injector: function(element
) {
1813 return JQLiteInheritedData(element
, '$injector');
1816 removeAttr: function(element
,name
) {
1817 element
.removeAttribute(name
);
1820 hasClass: JQLiteHasClass
,
1822 css: function(element
, name
, value
) {
1823 name
= camelCase(name
);
1825 if (isDefined(value
)) {
1826 element
.style
[name
] = value
;
1831 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why
1832 val
= element
.currentStyle
&& element
.currentStyle
[name
];
1833 if (val
=== '') val
= 'auto';
1836 val
= val
|| element
.style
[name
];
1839 // jquery weirdness :-/
1840 val
= (val
=== '') ? undefined : val
;
1847 attr: function(element
, name
, value
){
1848 var lowercasedName
= lowercase(name
);
1849 if (BOOLEAN_ATTR
[lowercasedName
]) {
1850 if (isDefined(value
)) {
1852 element
[name
] = true;
1853 element
.setAttribute(name
, lowercasedName
);
1855 element
[name
] = false;
1856 element
.removeAttribute(lowercasedName
);
1859 return (element
[name
] ||
1860 (element
.attributes
.getNamedItem(name
)|| noop
).specified
)
1864 } else if (isDefined(value
)) {
1865 element
.setAttribute(name
, value
);
1866 } else if (element
.getAttribute
) {
1867 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
1868 // some elements (e.g. Document) don't have get attribute, so return undefined
1869 var ret
= element
.getAttribute(name
, 2);
1870 // normalize non-existing attributes to undefined (as jQuery)
1871 return ret
=== null ? undefined : ret
;
1875 prop: function(element
, name
, value
) {
1876 if (isDefined(value
)) {
1877 element
[name
] = value
;
1879 return element
[name
];
1883 text: extend((msie
< 9)
1884 ? function(element
, value
) {
1885 if (element
.nodeType
== 1 /** Element */) {
1886 if (isUndefined(value
))
1887 return element
.innerText
;
1888 element
.innerText
= value
;
1890 if (isUndefined(value
))
1891 return element
.nodeValue
;
1892 element
.nodeValue
= value
;
1895 : function(element
, value
) {
1896 if (isUndefined(value
)) {
1897 return element
.textContent
;
1899 element
.textContent
= value
;
1902 val: function(element
, value
) {
1903 if (isUndefined(value
)) {
1904 return element
.value
;
1906 element
.value
= value
;
1909 html: function(element
, value
) {
1910 if (isUndefined(value
)) {
1911 return element
.innerHTML
;
1913 for (var i
= 0, childNodes
= element
.childNodes
; i
< childNodes
.length
; i
++) {
1914 JQLiteDealoc(childNodes
[i
]);
1916 element
.innerHTML
= value
;
1918 }, function(fn
, name
){
1920 * Properties: writes return selection, reads return first value
1922 JQLite
.prototype[name
] = function(arg1
, arg2
) {
1925 // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
1926 // in a way that survives minification.
1927 if (((fn
.length
== 2 && (fn
!== JQLiteHasClass
&& fn
!== JQLiteController
)) ? arg1 : arg2
) === undefined) {
1928 if (isObject(arg1
)) {
1930 // we are a write, but the object properties are the key/values
1931 for(i
=0; i
< this.length
; i
++) {
1932 if (fn
=== JQLiteData
) {
1933 // data() takes the whole object in jQuery
1937 fn(this[i
], key
, arg1
[key
]);
1941 // return self for chaining
1944 // we are a read, so read the first child.
1946 return fn(this[0], arg1
, arg2
);
1949 // we are a write, so apply to all children
1950 for(i
=0; i
< this.length
; i
++) {
1951 fn(this[i
], arg1
, arg2
);
1953 // return self for chaining
1960 function createEventHandler(element
, events
) {
1961 var eventHandler = function (event
, type
) {
1962 if (!event
.preventDefault
) {
1963 event
.preventDefault = function() {
1964 event
.returnValue
= false; //ie
1968 if (!event
.stopPropagation
) {
1969 event
.stopPropagation = function() {
1970 event
.cancelBubble
= true; //ie
1974 if (!event
.target
) {
1975 event
.target
= event
.srcElement
|| document
;
1978 if (isUndefined(event
.defaultPrevented
)) {
1979 var prevent
= event
.preventDefault
;
1980 event
.preventDefault = function() {
1981 event
.defaultPrevented
= true;
1982 prevent
.call(event
);
1984 event
.defaultPrevented
= false;
1987 event
.isDefaultPrevented = function() {
1988 return event
.defaultPrevented
;
1991 forEach(events
[type
|| event
.type
], function(fn
) {
1992 fn
.call(element
, event
);
1995 // Remove monkey-patched methods (IE),
1996 // as they would cause memory leaks in IE8.
1998 // IE7/8 does not allow to delete property on native object
1999 event
.preventDefault
= null;
2000 event
.stopPropagation
= null;
2001 event
.isDefaultPrevented
= null;
2003 // It shouldn't affect normal browsers (native methods are defined on prototype).
2004 delete event
.preventDefault
;
2005 delete event
.stopPropagation
;
2006 delete event
.isDefaultPrevented
;
2009 eventHandler
.elem
= element
;
2010 return eventHandler
;
2013 //////////////////////////////////////////
2014 // Functions iterating traversal.
2015 // These functions chain results into a single
2017 //////////////////////////////////////////
2019 removeData: JQLiteRemoveData
,
2021 dealoc: JQLiteDealoc
,
2023 bind: function bindFn(element
, type
, fn
){
2024 var events
= JQLiteExpandoStore(element
, 'events'),
2025 handle
= JQLiteExpandoStore(element
, 'handle');
2027 if (!events
) JQLiteExpandoStore(element
, 'events', events
= {});
2028 if (!handle
) JQLiteExpandoStore(element
, 'handle', handle
= createEventHandler(element
, events
));
2030 forEach(type
.split(' '), function(type
){
2031 var eventFns
= events
[type
];
2034 if (type
== 'mouseenter' || type
== 'mouseleave') {
2035 var contains
= document
.body
.contains
|| document
.body
.compareDocumentPosition
?
2037 var adown
= a
.nodeType
=== 9 ? a
.documentElement : a
,
2038 bup
= b
&& b
.parentNode
;
2039 return a
=== bup
|| !!( bup
&& bup
.nodeType
=== 1 && (
2041 adown
.contains( bup
) :
2042 a
.compareDocumentPosition
&& a
.compareDocumentPosition( bup
) & 16
2047 while ( (b
= b
.parentNode
) ) {
2058 // Refer to jQuery's implementation of mouseenter & mouseleave
2059 // Read about mouseenter and mouseleave:
2060 // http://www.quirksmode.org/js/events_mouse.html#link8
2061 var eventmap
= { mouseleave : "mouseout", mouseenter : "mouseover"}
2062 bindFn(element
, eventmap
[type
], function(event
) {
2063 var ret
, target
= this, related
= event
.relatedTarget
;
2064 // For mousenter/leave call the handler if related is outside the target.
2065 // NB: No relatedTarget if the mouse left/entered the browser window
2066 if ( !related
|| (related
!== target
&& !contains(target
, related
)) ){
2067 handle(event
, type
);
2073 addEventListenerFn(element
, type
, handle
);
2076 eventFns
= events
[type
]
2082 unbind: JQLiteUnbind
,
2084 replaceWith: function(element
, replaceNode
) {
2085 var index
, parent
= element
.parentNode
;
2086 JQLiteDealoc(element
);
2087 forEach(new JQLite(replaceNode
), function(node
){
2089 parent
.insertBefore(node
, index
.nextSibling
);
2091 parent
.replaceChild(node
, element
);
2097 children: function(element
) {
2099 forEach(element
.childNodes
, function(element
){
2100 if (element
.nodeType
=== 1)
2101 children
.push(element
);
2106 contents: function(element
) {
2107 return element
.childNodes
|| [];
2110 append: function(element
, node
) {
2111 forEach(new JQLite(node
), function(child
){
2112 if (element
.nodeType
=== 1)
2113 element
.appendChild(child
);
2117 prepend: function(element
, node
) {
2118 if (element
.nodeType
=== 1) {
2119 var index
= element
.firstChild
;
2120 forEach(new JQLite(node
), function(child
){
2122 element
.insertBefore(child
, index
);
2124 element
.appendChild(child
);
2131 wrap: function(element
, wrapNode
) {
2132 wrapNode
= jqLite(wrapNode
)[0];
2133 var parent
= element
.parentNode
;
2135 parent
.replaceChild(wrapNode
, element
);
2137 wrapNode
.appendChild(element
);
2140 remove: function(element
) {
2141 JQLiteDealoc(element
);
2142 var parent
= element
.parentNode
;
2143 if (parent
) parent
.removeChild(element
);
2146 after: function(element
, newElement
) {
2147 var index
= element
, parent
= element
.parentNode
;
2148 forEach(new JQLite(newElement
), function(node
){
2149 parent
.insertBefore(node
, index
.nextSibling
);
2154 addClass: JQLiteAddClass
,
2155 removeClass: JQLiteRemoveClass
,
2157 toggleClass: function(element
, selector
, condition
) {
2158 if (isUndefined(condition
)) {
2159 condition
= !JQLiteHasClass(element
, selector
);
2161 (condition
? JQLiteAddClass : JQLiteRemoveClass
)(element
, selector
);
2164 parent: function(element
) {
2165 var parent
= element
.parentNode
;
2166 return parent
&& parent
.nodeType
!== 11 ? parent : null;
2169 next: function(element
) {
2170 if (element
.nextElementSibling
) {
2171 return element
.nextElementSibling
;
2174 // IE8 doesn't have nextElementSibling
2175 var elm
= element
.nextSibling
;
2176 while (elm
!= null && elm
.nodeType
!== 1) {
2177 elm
= elm
.nextSibling
;
2182 find: function(element
, selector
) {
2183 return element
.getElementsByTagName(selector
);
2188 triggerHandler: function(element
, eventName
) {
2189 var eventFns
= (JQLiteExpandoStore(element
, 'events') || {})[eventName
];
2191 forEach(eventFns
, function(fn
) {
2192 fn
.call(element
, null);
2195 }, function(fn
, name
){
2197 * chaining functions
2199 JQLite
.prototype[name
] = function(arg1
, arg2
) {
2201 for(var i
=0; i
< this.length
; i
++) {
2202 if (value
== undefined) {
2203 value
= fn(this[i
], arg1
, arg2
);
2204 if (value
!== undefined) {
2205 // any function which returns a value needs to be wrapped
2206 value
= jqLite(value
);
2209 JQLiteAddNodes(value
, fn(this[i
], arg1
, arg2
));
2212 return value
== undefined ? this : value
;
2217 * Computes a hash of an 'obj'.
2220 * number is number as string
2221 * object is either result of calling $$hashKey function on the object or uniquely generated id,
2222 * that is also assigned to the $$hashKey property of the object.
2225 * @returns {string} hash string such that the same input will have the same hash string.
2226 * The resulting string key is in 'type:hashKey' format.
2228 function hashKey(obj
) {
2229 var objType
= typeof obj
,
2232 if (objType
== 'object' && obj
!== null) {
2233 if (typeof (key
= obj
.$$hashKey
) == 'function') {
2234 // must invoke on object to keep the right this
2235 key
= obj
.$$hashKey();
2236 } else if (key
=== undefined) {
2237 key
= obj
.$$hashKey
= nextUid();
2243 return objType
+ ':' + key
;
2247 * HashMap which can use objects as keys
2249 function HashMap(array
){
2250 forEach(array
, this.put
, this);
2252 HashMap
.prototype = {
2254 * Store key value pair
2255 * @param key key to store can be any type
2256 * @param value value to store can be any type
2258 put: function(key
, value
) {
2259 this[hashKey(key
)] = value
;
2264 * @returns the value for the key
2266 get: function(key
) {
2267 return this[hashKey(key
)];
2271 * Remove the key/value pair
2274 remove: function(key
) {
2275 var value
= this[key
= hashKey(key
)];
2282 * A map where multiple values can be added to the same key such that they form a queue.
2283 * @returns {HashQueueMap}
2285 function HashQueueMap() {}
2286 HashQueueMap
.prototype = {
2288 * Same as array push, but using an array as the value for the hash
2290 push: function(key
, value
) {
2291 var array
= this[key
= hashKey(key
)];
2293 this[key
] = [value
];
2300 * Same as array shift, but using an array as the value for the hash
2302 shift: function(key
) {
2303 var array
= this[key
= hashKey(key
)];
2305 if (array
.length
== 1) {
2309 return array
.shift();
2315 * return the first item without deleting it
2317 peek: function(key
) {
2318 var array
= this[hashKey(key
)];
2327 * @name angular.injector
2331 * Creates an injector function that can be used for retrieving services as well as for
2332 * dependency injection (see {@link guide/di dependency injection}).
2335 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
2336 * {@link angular.module}. The `ng` module must be explicitly added.
2337 * @returns {function()} Injector function. See {@link AUTO.$injector $injector}.
2342 * // create an injector
2343 * var $injector = angular.injector(['ng']);
2345 * // use the injector to kick off your application
2346 * // use the type inference to auto inject arguments, or use implicit injection
2347 * $injector.invoke(function($rootScope, $compile, $document){
2348 * $compile($document)($rootScope);
2349 * $rootScope.$digest();
2360 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
2363 var FN_ARGS
= /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
2364 var FN_ARG_SPLIT
= /,/;
2365 var FN_ARG
= /^\s*(_?)(\S+?)\1\s*$/;
2366 var STRIP_COMMENTS
= /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
2367 function annotate(fn
) {
2373 if (typeof fn
== 'function') {
2374 if (!($inject
= fn
.$inject
)) {
2376 fnText
= fn
.toString().replace(STRIP_COMMENTS
, '');
2377 argDecl
= fnText
.match(FN_ARGS
);
2378 forEach(argDecl
[1].split(FN_ARG_SPLIT
), function(arg
){
2379 arg
.replace(FN_ARG
, function(all
, underscore
, name
){
2383 fn
.$inject
= $inject
;
2385 } else if (isArray(fn
)) {
2386 last
= fn
.length
- 1;
2387 assertArgFn(fn
[last
], 'fn');
2388 $inject
= fn
.slice(0, last
);
2390 assertArgFn(fn
, 'fn', true);
2395 ///////////////////////////////////////
2399 * @name AUTO.$injector
2404 * `$injector` is used to retrieve object instances as defined by
2405 * {@link AUTO.$provide provider}, instantiate types, invoke methods,
2408 * The following always holds true:
2411 * var $injector = angular.injector();
2412 * expect($injector.get('$injector')).toBe($injector);
2413 * expect($injector.invoke(function($injector){
2415 * }).toBe($injector);
2418 * # Injection Function Annotation
2420 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
2421 * following are all valid ways of annotating function with injection arguments and are equivalent.
2424 * // inferred (only works if code not minified/obfuscated)
2425 * $injector.invoke(function(serviceA){});
2428 * function explicit(serviceA) {};
2429 * explicit.$inject = ['serviceA'];
2430 * $injector.invoke(explicit);
2433 * $injector.invoke(['serviceA', function(serviceA){}]);
2438 * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be
2439 * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation
2440 * tools since these tools change the argument names.
2442 * ## `$inject` Annotation
2443 * By adding a `$inject` property onto a function the injection parameters can be specified.
2446 * As an array of injection names, where the last item in the array is the function to call.
2451 * @name AUTO.$injector#get
2452 * @methodOf AUTO.$injector
2455 * Return an instance of the service.
2457 * @param {string} name The name of the instance to retrieve.
2458 * @return {*} The instance.
2463 * @name AUTO.$injector#invoke
2464 * @methodOf AUTO.$injector
2467 * Invoke the method and supply the method arguments from the `$injector`.
2469 * @param {!function} fn The function to invoke. The function arguments come form the function annotation.
2470 * @param {Object=} self The `this` for the invoked method.
2471 * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
2472 * the `$injector` is consulted.
2473 * @returns {*} the value returned by the invoked `fn` function.
2478 * @name AUTO.$injector#instantiate
2479 * @methodOf AUTO.$injector
2481 * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies
2482 * all of the arguments to the constructor function as specified by the constructor annotation.
2484 * @param {function} Type Annotated constructor function.
2485 * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
2486 * the `$injector` is consulted.
2487 * @returns {Object} new instance of `Type`.
2492 * @name AUTO.$injector#annotate
2493 * @methodOf AUTO.$injector
2496 * Returns an array of service names which the function is requesting for injection. This API is used by the injector
2497 * to determine which services need to be injected into the function when the function is invoked. There are three
2498 * ways in which the function can be annotated with the needed dependencies.
2502 * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
2503 * the function into a string using `toString()` method and extracting the argument names.
2506 * function MyController($scope, $route) {
2511 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
2514 * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
2517 * # The `$inject` property
2519 * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
2520 * services to be injected into the function.
2523 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
2526 * // Define function dependencies
2527 * MyController.$inject = ['$scope', '$route'];
2530 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
2533 * # The array notation
2535 * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
2536 * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
2537 * minification is a better choice:
2540 * // We wish to write this (not minification / obfuscation safe)
2541 * injector.invoke(function($compile, $rootScope) {
2545 * // We are forced to write break inlining
2546 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
2549 * tmpFn.$inject = ['$compile', '$rootScope'];
2550 * injector.invoke(tmpFn);
2552 * // To better support inline function the inline annotation is supported
2553 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
2558 * expect(injector.annotate(
2559 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
2560 * ).toEqual(['$compile', '$rootScope']);
2563 * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
2566 * @returns {Array.<string>} The names of the services which the function requires.
2574 * @name AUTO.$provide
2578 * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
2579 * The providers share the same name as the instance they create with `Provider` suffixed to them.
2581 * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
2582 * a service. The Provider can have additional methods which would allow for configuration of the provider.
2585 * function GreetProvider() {
2586 * var salutation = 'Hello';
2588 * this.salutation = function(text) {
2589 * salutation = text;
2592 * this.$get = function() {
2593 * return function (name) {
2594 * return salutation + ' ' + name + '!';
2599 * describe('Greeter', function(){
2601 * beforeEach(module(function($provide) {
2602 * $provide.provider('greet', GreetProvider);
2605 * it('should greet', inject(function(greet) {
2606 * expect(greet('angular')).toEqual('Hello angular!');
2609 * it('should allow configuration of salutation', function() {
2610 * module(function(greetProvider) {
2611 * greetProvider.salutation('Ahoj');
2613 * inject(function(greet) {
2614 * expect(greet('angular')).toEqual('Ahoj angular!');
2622 * @name AUTO.$provide#provider
2623 * @methodOf AUTO.$provide
2626 * Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
2628 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key.
2629 * @param {(Object|function())} provider If the provider is:
2631 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
2632 * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
2633 * - `Constructor`: a new instance of the provider will be created using
2634 * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
2636 * @returns {Object} registered provider instance
2641 * @name AUTO.$provide#factory
2642 * @methodOf AUTO.$provide
2645 * A short hand for configuring services if only `$get` method is required.
2647 * @param {string} name The name of the instance.
2648 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for
2649 * `$provide.provider(name, {$get: $getFn})`.
2650 * @returns {Object} registered provider instance
2656 * @name AUTO.$provide#service
2657 * @methodOf AUTO.$provide
2660 * A short hand for registering service of given class.
2662 * @param {string} name The name of the instance.
2663 * @param {Function} constructor A class (constructor function) that will be instantiated.
2664 * @returns {Object} registered provider instance
2670 * @name AUTO.$provide#value
2671 * @methodOf AUTO.$provide
2674 * A short hand for configuring services if the `$get` method is a constant.
2676 * @param {string} name The name of the instance.
2677 * @param {*} value The value.
2678 * @returns {Object} registered provider instance
2684 * @name AUTO.$provide#constant
2685 * @methodOf AUTO.$provide
2688 * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected
2689 * into configuration function (other modules) and it is not interceptable by
2690 * {@link AUTO.$provide#decorator decorator}.
2692 * @param {string} name The name of the constant.
2693 * @param {*} value The constant value.
2694 * @returns {Object} registered instance
2700 * @name AUTO.$provide#decorator
2701 * @methodOf AUTO.$provide
2704 * Decoration of service, allows the decorator to intercept the service instance creation. The
2705 * returned instance may be the original instance, or a new instance which delegates to the
2706 * original instance.
2708 * @param {string} name The name of the service to decorate.
2709 * @param {function()} decorator This function will be invoked when the service needs to be
2710 * instantiated. The function is called using the {@link AUTO.$injector#invoke
2711 * injector.invoke} method and is therefore fully injectable. Local injection arguments:
2713 * * `$delegate` - The original service instance, which can be monkey patched, configured,
2714 * decorated or delegated to.
2718 function createInjector(modulesToLoad
) {
2719 var INSTANTIATING
= {},
2720 providerSuffix
= 'Provider',
2722 loadedModules
= new HashMap(),
2725 provider: supportObject(provider
),
2726 factory: supportObject(factory
),
2727 service: supportObject(service
),
2728 value: supportObject(value
),
2729 constant: supportObject(constant
),
2730 decorator: decorator
2733 providerInjector
= createInternalInjector(providerCache
, function() {
2734 throw Error("Unknown provider: " + path
.join(' <- '));
2737 instanceInjector
= (instanceCache
.$injector
=
2738 createInternalInjector(instanceCache
, function(servicename
) {
2739 var provider
= providerInjector
.get(servicename
+ providerSuffix
);
2740 return instanceInjector
.invoke(provider
.$get, provider
);
2744 forEach(loadModules(modulesToLoad
), function(fn
) { instanceInjector
.invoke(fn
|| noop
); });
2746 return instanceInjector
;
2748 ////////////////////////////////////
2750 ////////////////////////////////////
2752 function supportObject(delegate
) {
2753 return function(key
, value
) {
2754 if (isObject(key
)) {
2755 forEach(key
, reverseParams(delegate
));
2757 return delegate(key
, value
);
2762 function provider(name
, provider_
) {
2763 if (isFunction(provider_
) || isArray(provider_
)) {
2764 provider_
= providerInjector
.instantiate(provider_
);
2766 if (!provider_
.$get) {
2767 throw Error('Provider ' + name
+ ' must define $get factory method.');
2769 return providerCache
[name
+ providerSuffix
] = provider_
;
2772 function factory(name
, factoryFn
) { return provider(name
, { $get: factoryFn
}); }
2774 function service(name
, constructor) {
2775 return factory(name
, ['$injector', function($injector
) {
2776 return $injector
.instantiate(constructor);
2780 function value(name
, value
) { return factory(name
, valueFn(value
)); }
2782 function constant(name
, value
) {
2783 providerCache
[name
] = value
;
2784 instanceCache
[name
] = value
;
2787 function decorator(serviceName
, decorFn
) {
2788 var origProvider
= providerInjector
.get(serviceName
+ providerSuffix
),
2789 orig
$get = origProvider
.$get;
2791 origProvider
.$get = function() {
2792 var origInstance
= instanceInjector
.invoke(orig
$get, origProvider
);
2793 return instanceInjector
.invoke(decorFn
, null, {$delegate: origInstance
});
2797 ////////////////////////////////////
2799 ////////////////////////////////////
2800 function loadModules(modulesToLoad
){
2802 forEach(modulesToLoad
, function(module
) {
2803 if (loadedModules
.get(module
)) return;
2804 loadedModules
.put(module
, true);
2805 if (isString(module
)) {
2806 var moduleFn
= angularModule(module
);
2807 runBlocks
= runBlocks
.concat(loadModules(moduleFn
.requires
)).concat(moduleFn
._runBlocks
);
2810 for(var invokeQueue
= moduleFn
._invokeQueue
, i
= 0, ii
= invokeQueue
.length
; i
< ii
; i
++) {
2811 var invokeArgs
= invokeQueue
[i
],
2812 provider
= invokeArgs
[0] == '$injector'
2814 : providerInjector
.get(invokeArgs
[0]);
2816 provider
[invokeArgs
[1]].apply(provider
, invokeArgs
[2]);
2819 if (e
.message
) e
.message
+= ' from ' + module
;
2822 } else if (isFunction(module
)) {
2824 runBlocks
.push(providerInjector
.invoke(module
));
2826 if (e
.message
) e
.message
+= ' from ' + module
;
2829 } else if (isArray(module
)) {
2831 runBlocks
.push(providerInjector
.invoke(module
));
2833 if (e
.message
) e
.message
+= ' from ' + String(module
[module
.length
- 1]);
2837 assertArgFn(module
, 'module');
2843 ////////////////////////////////////
2844 // internal Injector
2845 ////////////////////////////////////
2847 function createInternalInjector(cache
, factory
) {
2849 function getService(serviceName
) {
2850 if (typeof serviceName
!== 'string') {
2851 throw Error('Service name expected');
2853 if (cache
.hasOwnProperty(serviceName
)) {
2854 if (cache
[serviceName
] === INSTANTIATING
) {
2855 throw Error('Circular dependency: ' + path
.join(' <- '));
2857 return cache
[serviceName
];
2860 path
.unshift(serviceName
);
2861 cache
[serviceName
] = INSTANTIATING
;
2862 return cache
[serviceName
] = factory(serviceName
);
2869 function invoke(fn
, self
, locals
){
2871 $inject
= annotate(fn
),
2875 for(i
= 0, length
= $inject
.length
; i
< length
; i
++) {
2878 locals
&& locals
.hasOwnProperty(key
)
2884 // this means that we must be an array.
2889 // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
2890 switch (self
? -1 : args
.length
) {
2891 case 0: return fn();
2892 case 1: return fn(args
[0]);
2893 case 2: return fn(args
[0], args
[1]);
2894 case 3: return fn(args
[0], args
[1], args
[2]);
2895 case 4: return fn(args
[0], args
[1], args
[2], args
[3]);
2896 case 5: return fn(args
[0], args
[1], args
[2], args
[3], args
[4]);
2897 case 6: return fn(args
[0], args
[1], args
[2], args
[3], args
[4], args
[5]);
2898 case 7: return fn(args
[0], args
[1], args
[2], args
[3], args
[4], args
[5], args
[6]);
2899 case 8: return fn(args
[0], args
[1], args
[2], args
[3], args
[4], args
[5], args
[6], args
[7]);
2900 case 9: return fn(args
[0], args
[1], args
[2], args
[3], args
[4], args
[5], args
[6], args
[7], args
[8]);
2901 case 10: return fn(args
[0], args
[1], args
[2], args
[3], args
[4], args
[5], args
[6], args
[7], args
[8], args
[9]);
2902 default: return fn
.apply(self
, args
);
2906 function instantiate(Type
, locals
) {
2907 var Constructor = function() {},
2908 instance
, returnedValue
;
2910 // Check if Type is annotated and use just the given function at n-1 as parameter
2911 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
2912 Constructor
.prototype = (isArray(Type
) ? Type
[Type
.length
- 1] : Type
).prototype;
2913 instance
= new Constructor();
2914 returnedValue
= invoke(Type
, instance
, locals
);
2916 return isObject(returnedValue
) ? returnedValue : instance
;
2921 instantiate: instantiate
,
2930 * @name ng.$anchorScroll
2932 * @requires $location
2933 * @requires $rootScope
2936 * When called, it checks current value of `$location.hash()` and scroll to related element,
2937 * according to rules specified in
2938 * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
2940 * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
2941 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
2943 function $AnchorScrollProvider() {
2945 var autoScrollingEnabled
= true;
2947 this.disableAutoScrolling = function() {
2948 autoScrollingEnabled
= false;
2951 this.$get = ['$window', '$location', '$rootScope', function($window
, $location
, $rootScope
) {
2952 var document
= $window
.document
;
2954 // helper function to get first anchor from a NodeList
2955 // can't use filter.filter, as it accepts only instances of Array
2956 // and IE can't convert NodeList to an array using [].slice
2957 // TODO(vojta): use filter if we change it to accept lists as well
2958 function getFirstAnchor(list
) {
2960 forEach(list
, function(element
) {
2961 if (!result
&& lowercase(element
.nodeName
) === 'a') result
= element
;
2967 var hash
= $location
.hash(), elm
;
2969 // empty hash, scroll to the top of the page
2970 if (!hash
) $window
.scrollTo(0, 0);
2972 // element with given id
2973 else if ((elm
= document
.getElementById(hash
))) elm
.scrollIntoView();
2975 // first anchor with given name :-D
2976 else if ((elm
= getFirstAnchor(document
.getElementsByName(hash
)))) elm
.scrollIntoView();
2978 // no element and hash == 'top', scroll to the top of the page
2979 else if (hash
=== 'top') $window
.scrollTo(0, 0);
2982 // does not scroll when user clicks on anchor link that is currently on
2983 // (no url change, no $location.hash() change), browser native does scroll
2984 if (autoScrollingEnabled
) {
2985 $rootScope
.$watch(function autoScrollWatch() {return $location
.hash();},
2986 function autoScrollWatchAction() {
2987 $rootScope
.$evalAsync(scroll
);
2996 * ! This is a private undocumented service !
3001 * This object has two goals:
3003 * - hide all the global state in the browser caused by the window object
3004 * - abstract away all the browser specific features and inconsistencies
3006 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
3007 * service, which can be used for convenient testing of the application without the interaction with
3008 * the real browser apis.
3011 * @param {object} window The global window object.
3012 * @param {object} document jQuery wrapped document.
3013 * @param {function()} XHR XMLHttpRequest constructor.
3014 * @param {object} $log console.log or an object with the same interface.
3015 * @param {object} $sniffer $sniffer service
3017 function Browser(window
, document
, $log
, $sniffer
) {
3019 rawDocument
= document
[0],
3020 location
= window
.location
,
3021 history
= window
.history
,
3022 setTimeout
= window
.setTimeout
,
3023 clearTimeout
= window
.clearTimeout
,
3024 pendingDeferIds
= {};
3026 self
.isMock
= false;
3028 var outstandingRequestCount
= 0;
3029 var outstandingRequestCallbacks
= [];
3031 // TODO(vojta): remove this temporary api
3032 self
.$$completeOutstandingRequest
= completeOutstandingRequest
;
3033 self
.$$incOutstandingRequestCount = function() { outstandingRequestCount
++; };
3036 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
3037 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
3039 function completeOutstandingRequest(fn
) {
3041 fn
.apply(null, sliceArgs(arguments
, 1));
3043 outstandingRequestCount
--;
3044 if (outstandingRequestCount
=== 0) {
3045 while(outstandingRequestCallbacks
.length
) {
3047 outstandingRequestCallbacks
.pop()();
3058 * Note: this method is used only by scenario runner
3059 * TODO(vojta): prefix this method with $$ ?
3060 * @param {function()} callback Function that will be called when no outstanding request
3062 self
.notifyWhenNoOutstandingRequests = function(callback
) {
3063 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
3064 // at some deterministic time in respect to the test runner's actions. Leaving things up to the
3065 // regular poller would result in flaky tests.
3066 forEach(pollFns
, function(pollFn
){ pollFn(); });
3068 if (outstandingRequestCount
=== 0) {
3071 outstandingRequestCallbacks
.push(callback
);
3075 //////////////////////////////////////////////////////////////
3077 //////////////////////////////////////////////////////////////
3082 * @name ng.$browser#addPollFn
3083 * @methodOf ng.$browser
3085 * @param {function()} fn Poll function to add
3088 * Adds a function to the list of functions that poller periodically executes,
3089 * and starts polling if not started yet.
3091 * @returns {function()} the added function
3093 self
.addPollFn = function(fn
) {
3094 if (isUndefined(pollTimeout
)) startPoller(100, setTimeout
);
3100 * @param {number} interval How often should browser call poll functions (ms)
3101 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
3104 * Configures the poller to run in the specified intervals, using the specified
3105 * setTimeout fn and kicks it off.
3107 function startPoller(interval
, setTimeout
) {
3109 forEach(pollFns
, function(pollFn
){ pollFn(); });
3110 pollTimeout
= setTimeout(check
, interval
);
3114 //////////////////////////////////////////////////////////////
3116 //////////////////////////////////////////////////////////////
3118 var lastBrowserUrl
= location
.href
,
3119 baseElement
= document
.find('base');
3122 * @name ng.$browser#url
3123 * @methodOf ng.$browser
3127 * Without any argument, this method just returns current value of location.href.
3130 * With at least one argument, this method sets url to new value.
3131 * If html5 history api supported, pushState/replaceState is used, otherwise
3132 * location.href/location.replace is used.
3133 * Returns its own instance to allow chaining
3135 * NOTE: this api is intended for use only by the $location service. Please use the
3136 * {@link ng.$location $location service} to change url.
3138 * @param {string} url New url (when used as setter)
3139 * @param {boolean=} replace Should new url replace current history record ?
3141 self
.url = function(url
, replace
) {
3144 if (lastBrowserUrl
== url
) return;
3145 lastBrowserUrl
= url
;
3146 if ($sniffer
.history
) {
3147 if (replace
) history
.replaceState(null, '', url
);
3149 history
.pushState(null, '', url
);
3150 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
3151 baseElement
.attr('href', baseElement
.attr('href'));
3154 if (replace
) location
.replace(url
);
3155 else location
.href
= url
;
3160 // the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
3161 return location
.href
.replace(/%27/g,"'");
3165 var urlChangeListeners
= [],
3166 urlChangeInit
= false;
3168 function fireUrlChange() {
3169 if (lastBrowserUrl
== self
.url()) return;
3171 lastBrowserUrl
= self
.url();
3172 forEach(urlChangeListeners
, function(listener
) {
3173 listener(self
.url());
3178 * @name ng.$browser#onUrlChange
3179 * @methodOf ng.$browser
3180 * @TODO(vojta): refactor to use node's syntax for events
3183 * Register callback function that will be called, when url changes.
3185 * It's only called when the url is changed by outside of angular:
3186 * - user types different url into address bar
3187 * - user clicks on history (forward/back) button
3188 * - user clicks on a link
3190 * It's not called when url is changed by $browser.url() method
3192 * The listener gets called with new url as parameter.
3194 * NOTE: this api is intended for use only by the $location service. Please use the
3195 * {@link ng.$location $location service} to monitor url changes in angular apps.
3197 * @param {function(string)} listener Listener function to be called when url changes.
3198 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
3200 self
.onUrlChange = function(callback
) {
3201 if (!urlChangeInit
) {
3202 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
3203 // don't fire popstate when user change the address bar and don't fire hashchange when url
3204 // changed by push/replaceState
3206 // html5 history api - popstate event
3207 if ($sniffer
.history
) jqLite(window
).bind('popstate', fireUrlChange
);
3209 if ($sniffer
.hashchange
) jqLite(window
).bind('hashchange', fireUrlChange
);
3211 else self
.addPollFn(fireUrlChange
);
3213 urlChangeInit
= true;
3216 urlChangeListeners
.push(callback
);
3220 //////////////////////////////////////////////////////////////
3222 //////////////////////////////////////////////////////////////
3225 * Returns current <base href>
3226 * (always relative - without domain)
3228 * @returns {string=}
3230 self
.baseHref = function() {
3231 var href
= baseElement
.attr('href');
3232 return href
? href
.replace(/^https?\:\/\/[^\/]*/, '') : '';
3235 //////////////////////////////////////////////////////////////
3237 //////////////////////////////////////////////////////////////
3238 var lastCookies
= {};
3239 var lastCookieString
= '';
3240 var cookiePath
= self
.baseHref();
3243 * @name ng.$browser#cookies
3244 * @methodOf ng.$browser
3246 * @param {string=} name Cookie name
3247 * @param {string=} value Cokkie value
3250 * The cookies method provides a 'private' low level access to browser cookies.
3251 * It is not meant to be used directly, use the $cookie service instead.
3253 * The return values vary depending on the arguments that the method was called with as follows:
3255 * <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
3256 * <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
3257 * <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
3260 * @returns {Object} Hash of all cookies (if called without any parameter)
3262 self
.cookies = function(name
, value
) {
3263 var cookieLength
, cookieArray
, cookie
, i
, index
;
3266 if (value
=== undefined) {
3267 rawDocument
.cookie
= escape(name
) + "=;path=" + cookiePath
+ ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
3269 if (isString(value
)) {
3270 cookieLength
= (rawDocument
.cookie
= escape(name
) + '=' + escape(value
) + ';path=' + cookiePath
).length
+ 1;
3272 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
3274 // - 20 cookies per unique domain
3275 // - 4096 bytes per cookie
3276 if (cookieLength
> 4096) {
3277 $log
.warn("Cookie '"+ name
+"' possibly not set or overflowed because it was too large ("+
3278 cookieLength
+ " > 4096 bytes)!");
3283 if (rawDocument
.cookie
!== lastCookieString
) {
3284 lastCookieString
= rawDocument
.cookie
;
3285 cookieArray
= lastCookieString
.split("; ");
3288 for (i
= 0; i
< cookieArray
.length
; i
++) {
3289 cookie
= cookieArray
[i
];
3290 index
= cookie
.indexOf('=');
3291 if (index
> 0) { //ignore nameless cookies
3292 var name
= unescape(cookie
.substring(0, index
));
3293 // the first value that is seen for a cookie is the most
3294 // specific one. values for the same cookie name that
3295 // follow are for less specific paths.
3296 if (lastCookies
[name
] === undefined) {
3297 lastCookies
[name
] = unescape(cookie
.substring(index
+ 1));
3308 * @name ng.$browser#defer
3309 * @methodOf ng.$browser
3310 * @param {function()} fn A function, who's execution should be defered.
3311 * @param {number=} [delay=0] of milliseconds to defer the function execution.
3312 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
3315 * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
3317 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
3318 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
3319 * via `$browser.defer.flush()`.
3322 self
.defer = function(fn
, delay
) {
3324 outstandingRequestCount
++;
3325 timeoutId
= setTimeout(function() {
3326 delete pendingDeferIds
[timeoutId
];
3327 completeOutstandingRequest(fn
);
3329 pendingDeferIds
[timeoutId
] = true;
3335 * @name ng.$browser#defer.cancel
3336 * @methodOf ng.$browser.defer
3339 * Cancels a defered task identified with `deferId`.
3341 * @param {*} deferId Token returned by the `$browser.defer` function.
3342 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
3344 self
.defer
.cancel = function(deferId
) {
3345 if (pendingDeferIds
[deferId
]) {
3346 delete pendingDeferIds
[deferId
];
3347 clearTimeout(deferId
);
3348 completeOutstandingRequest(noop
);
3356 function $BrowserProvider(){
3357 this.$get = ['$window', '$log', '$sniffer', '$document',
3358 function( $window
, $log
, $sniffer
, $document
){
3359 return new Browser($window
, $document
, $log
, $sniffer
);
3365 * @name ng.$cacheFactory
3368 * Factory that constructs cache objects.
3371 * @param {string} cacheId Name or id of the newly created cache.
3372 * @param {object=} options Options object that specifies the cache behavior. Properties:
3374 * - `{number=}` `capacity` — turns the cache into LRU cache.
3376 * @returns {object} Newly created cache object with the following set of methods:
3378 * - `{object}` `info()` — Returns id, size, and options of cache.
3379 * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
3380 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
3381 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
3382 * - `{void}` `removeAll()` — Removes all cached values.
3383 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
3386 function $CacheFactoryProvider() {
3388 this.$get = function() {
3391 function cacheFactory(cacheId
, options
) {
3392 if (cacheId
in caches
) {
3393 throw Error('cacheId ' + cacheId
+ ' taken');
3397 stats
= extend({}, options
, {id: cacheId
}),
3399 capacity
= (options
&& options
.capacity
) || Number
.MAX_VALUE
,
3404 return caches
[cacheId
] = {
3406 put: function(key
, value
) {
3407 var lruEntry
= lruHash
[key
] || (lruHash
[key
] = {key: key
});
3411 if (isUndefined(value
)) return;
3412 if (!(key
in data
)) size
++;
3415 if (size
> capacity
) {
3416 this.remove(staleEnd
.key
);
3421 get: function(key
) {
3422 var lruEntry
= lruHash
[key
];
3424 if (!lruEntry
) return;
3432 remove: function(key
) {
3433 var lruEntry
= lruHash
[key
];
3435 if (!lruEntry
) return;
3437 if (lruEntry
== freshEnd
) freshEnd
= lruEntry
.p
;
3438 if (lruEntry
== staleEnd
) staleEnd
= lruEntry
.n
;
3439 link(lruEntry
.n
,lruEntry
.p
);
3441 delete lruHash
[key
];
3447 removeAll: function() {
3451 freshEnd
= staleEnd
= null;
3455 destroy: function() {
3459 delete caches
[cacheId
];
3464 return extend({}, stats
, {size: size
});
3470 * makes the `entry` the freshEnd of the LRU linked list
3472 function refresh(entry
) {
3473 if (entry
!= freshEnd
) {
3476 } else if (staleEnd
== entry
) {
3480 link(entry
.n
, entry
.p
);
3481 link(entry
, freshEnd
);
3489 * bidirectionally links two entries of the LRU linked list
3491 function link(nextEntry
, prevEntry
) {
3492 if (nextEntry
!= prevEntry
) {
3493 if (nextEntry
) nextEntry
.p
= prevEntry
; //p stands for previous, 'prev' didn't minify
3494 if (prevEntry
) prevEntry
.n
= nextEntry
; //n stands for next, 'next' didn't minify
3500 cacheFactory
.info = function() {
3502 forEach(caches
, function(cache
, cacheId
) {
3503 info
[cacheId
] = cache
.info();
3509 cacheFactory
.get = function(cacheId
) {
3510 return caches
[cacheId
];
3514 return cacheFactory
;
3520 * @name ng.$templateCache
3523 * Cache used for storing html templates.
3525 * See {@link ng.$cacheFactory $cacheFactory}.
3528 function $TemplateCacheProvider() {
3529 this.$get = ['$cacheFactory', function($cacheFactory
) {
3530 return $cacheFactory('templates');
3534 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
3536 * DOM-related variables:
3538 * - "node" - DOM Node
3539 * - "element" - DOM Element or Node
3540 * - "$node" or "$element" - jqLite-wrapped node or element
3543 * Compiler related stuff:
3545 * - "linkFn" - linking fn of a single directive
3546 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
3547 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
3548 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
3552 var NON_ASSIGNABLE_MODEL_EXPRESSION
= 'Non-assignable model expression: ';
3561 * Compiles a piece of HTML string or DOM into a template and produces a template function, which
3562 * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
3564 * The compilation is a process of walking the DOM tree and trying to match DOM elements to
3565 * {@link ng.$compileProvider#directive directives}. For each match it
3566 * executes corresponding template function and collects the
3567 * instance functions into a single template function which is then returned.
3569 * The template function can then be used once to produce the view or as it is the case with
3570 * {@link ng.directive:ngRepeat repeater} many-times, in which
3571 * case each call results in a view that is a DOM clone of the original template.
3573 <doc:example module="compile">
3576 // declare a new module, and inject the $compileProvider
3577 angular.module('compile', [], function($compileProvider) {
3578 // configure new 'compile' directive by passing a directive
3579 // factory function. The factory function injects the '$compile'
3580 $compileProvider.directive('compile', function($compile) {
3581 // directive factory creates a link function
3582 return function(scope, element, attrs) {
3585 // watch the 'compile' expression for changes
3586 return scope.$eval(attrs.compile);
3589 // when the 'compile' expression changes
3590 // assign it into the current DOM
3591 element.html(value);
3593 // compile the new DOM and link it to the current
3595 // NOTE: we only compile .childNodes so that
3596 // we don't get into infinite loop compiling ourselves
3597 $compile(element.contents())(scope);
3604 function Ctrl($scope) {
3605 $scope.name = 'Angular';
3606 $scope.html = 'Hello {{name}}';
3609 <div ng-controller="Ctrl">
3610 <input ng-model="name"> <br>
3611 <textarea ng-model="html"></textarea> <br>
3612 <div compile="html"></div>
3616 it('should auto compile', function() {
3617 expect(element('div[compile]').text()).toBe('Hello Angular');
3618 input('html').enter('{{name}}!');
3619 expect(element('div[compile]').text()).toBe('Angular!');
3626 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
3627 * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
3628 * @param {number} maxPriority only apply directives lower then given priority (Only effects the
3629 * root element(s), not their children)
3630 * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
3631 * (a DOM element/tree) to a scope. Where:
3633 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
3634 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
3635 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
3636 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
3637 * called as: <br> `cloneAttachFn(clonedElement, scope)` where:
3639 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
3640 * * `scope` - is the current scope with which the linking function is working with.
3642 * Calling the linking function returns the element of the template. It is either the original element
3643 * passed in, or the clone of the element if the `cloneAttachFn` is provided.
3645 * After linking the view is not updated until after a call to $digest which typically is done by
3646 * Angular automatically.
3648 * If you need access to the bound view, there are two ways to do it:
3650 * - If you are not asking the linking function to clone the template, create the DOM element(s)
3651 * before you send them to the compiler and keep this reference around.
3653 * var element = $compile('<p>{{total}}</p>')(scope);
3656 * - if on the other hand, you need the element to be cloned, the view reference from the original
3657 * example would not point to the clone, but rather to the original template that was cloned. In
3658 * this case, you can access the clone via the cloneAttachFn:
3660 * var templateHTML = angular.element('<p>{{total}}</p>'),
3663 * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
3664 * //attach the clone to DOM document at the right place
3667 * //now we have reference to the cloned DOM via `clone`
3671 * For information on how the compiler works, see the
3672 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
3678 * @name ng.$compileProvider
3683 $CompileProvider
.$inject
= ['$provide'];
3684 function $CompileProvider($provide
) {
3685 var hasDirectives
= {},
3686 Suffix
= 'Directive',
3687 COMMENT_DIRECTIVE_REGEXP
= /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
3688 CLASS_DIRECTIVE_REGEXP
= /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
3689 MULTI_ROOT_TEMPLATE_ERROR
= 'Template must have exactly one root element. was: ',
3690 urlSanitizationWhitelist
= /^\s*(https?|ftp|mailto|file):/;
3695 * @name ng.$compileProvider#directive
3696 * @methodOf ng.$compileProvider
3700 * Register a new directives with the compiler.
3702 * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
3703 * <code>ng-bind</code>).
3704 * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
3706 * @returns {ng.$compileProvider} Self for chaining.
3708 this.directive
= function registerDirective(name
, directiveFactory
) {
3709 if (isString(name
)) {
3710 assertArg(directiveFactory
, 'directive');
3711 if (!hasDirectives
.hasOwnProperty(name
)) {
3712 hasDirectives
[name
] = [];
3713 $provide
.factory(name
+ Suffix
, ['$injector', '$exceptionHandler',
3714 function($injector
, $exceptionHandler
) {
3715 var directives
= [];
3716 forEach(hasDirectives
[name
], function(directiveFactory
) {
3718 var directive
= $injector
.invoke(directiveFactory
);
3719 if (isFunction(directive
)) {
3720 directive
= { compile: valueFn(directive
) };
3721 } else if (!directive
.compile
&& directive
.link
) {
3722 directive
.compile
= valueFn(directive
.link
);
3724 directive
.priority
= directive
.priority
|| 0;
3725 directive
.name
= directive
.name
|| name
;
3726 directive
.require
= directive
.require
|| (directive
.controller
&& directive
.name
);
3727 directive
.restrict
= directive
.restrict
|| 'A';
3728 directives
.push(directive
);
3730 $exceptionHandler(e
);
3736 hasDirectives
[name
].push(directiveFactory
);
3738 forEach(name
, reverseParams(registerDirective
));
3746 * @name ng.$compileProvider#urlSanitizationWhitelist
3747 * @methodOf ng.$compileProvider
3751 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
3752 * urls during a[href] sanitization.
3754 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
3756 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an
3757 * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular
3758 * expression. If a match is found the original url is written into the dom. Otherwise the
3759 * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM.
3761 * @param {RegExp=} regexp New regexp to whitelist urls with.
3762 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
3763 * chaining otherwise.
3765 this.urlSanitizationWhitelist = function(regexp
) {
3766 if (isDefined(regexp
)) {
3767 urlSanitizationWhitelist
= regexp
;
3770 return urlSanitizationWhitelist
;
3775 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
3776 '$controller', '$rootScope', '$document',
3777 function($injector
, $interpolate
, $exceptionHandler
, $http
, $templateCache
, $parse
,
3778 $controller
, $rootScope
, $document
) {
3780 var Attributes = function(element
, attr
) {
3781 this.$$element
= element
;
3782 this.$attr
= attr
|| {};
3785 Attributes
.prototype = {
3786 $normalize: directiveNormalize
,
3790 * Set a normalized attribute on the element in a way such that all directives
3791 * can share the attribute. This function properly handles boolean attributes.
3792 * @param {string} key Normalized key. (ie ngAttribute)
3793 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
3794 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
3796 * @param {string=} attrName Optional none normalized name. Defaults to key.
3798 $set: function(key
, value
, writeAttr
, attrName
) {
3799 var booleanKey
= getBooleanAttrName(this.$$element
[0], key
),
3800 $$observers
= this.$$observers
,
3804 this.$$element
.prop(key
, value
);
3805 attrName
= booleanKey
;
3810 // translate normalized key to actual key
3812 this.$attr
[key
] = attrName
;
3814 attrName
= this.$attr
[key
];
3816 this.$attr
[key
] = attrName
= snake_case(key
, '-');
3821 // sanitize a[href] values
3822 if (nodeName_(this.$$element
[0]) === 'A' && key
=== 'href') {
3823 urlSanitizationNode
.setAttribute('href', value
);
3825 // href property always returns normalized absolute url, so we can match against that
3826 normalizedVal
= urlSanitizationNode
.href
;
3827 if (!normalizedVal
.match(urlSanitizationWhitelist
)) {
3828 this[key
] = value
= 'unsafe:' + normalizedVal
;
3833 if (writeAttr
!== false) {
3834 if (value
=== null || value
=== undefined) {
3835 this.$$element
.removeAttr(attrName
);
3837 this.$$element
.attr(attrName
, value
);
3842 $$observers
&& forEach($$observers
[key
], function(fn
) {
3846 $exceptionHandler(e
);
3853 * Observe an interpolated attribute.
3854 * The observer will never be called, if given attribute is not interpolated.
3856 * @param {string} key Normalized key. (ie ngAttribute) .
3857 * @param {function(*)} fn Function that will be called whenever the attribute value changes.
3858 * @returns {function(*)} the `fn` Function passed in.
3860 $observe: function(key
, fn
) {
3862 $$observers
= (attrs
.$$observers
|| (attrs
.$$observers
= {})),
3863 listeners
= ($$observers
[key
] || ($$observers
[key
] = []));
3866 $rootScope
.$evalAsync(function() {
3867 if (!listeners
.$$inter
) {
3868 // no one registered attribute interpolation function, so lets call it manually
3876 var urlSanitizationNode
= $document
[0].createElement('a'),
3877 startSymbol
= $interpolate
.startSymbol(),
3878 endSymbol
= $interpolate
.endSymbol(),
3879 denormalizeTemplate
= (startSymbol
== '{{' || endSymbol
== '}}')
3881 : function denormalizeTemplate(template
) {
3882 return template
.replace(/\{\{/g, startSymbol
).replace(/}}/g, endSymbol
);
3888 //================================
3890 function compile($compileNodes
, transcludeFn
, maxPriority
) {
3891 if (!($compileNodes
instanceof jqLite
)) {
3892 // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
3893 $compileNodes
= jqLite($compileNodes
);
3895 // We can not compile top level text elements since text nodes can be merged and we will
3896 // not be able to attach scope data to them, so we will wrap them in <span>
3897 forEach($compileNodes
, function(node
, index
){
3898 if (node
.nodeType
== 3 /* text node */ && node
.nodeValue
.match(/\S+/) /* non-empty */ ) {
3899 $compileNodes
[index
] = jqLite(node
).wrap('<span></span>').parent()[0];
3902 var compositeLinkFn
= compileNodes($compileNodes
, transcludeFn
, $compileNodes
, maxPriority
);
3903 return function publicLinkFn(scope
, cloneConnectFn
){
3904 assertArg(scope
, 'scope');
3905 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
3906 // and sometimes changes the structure of the DOM.
3907 var $linkNode
= cloneConnectFn
3908 ? JQLitePrototype
.clone
.call($compileNodes
) // IMPORTANT!!!
3911 // Attach scope only to non-text nodes.
3912 for(var i
= 0, ii
= $linkNode
.length
; i
<ii
; i
++) {
3913 var node
= $linkNode
[i
];
3914 if (node
.nodeType
== 1 /* element */ || node
.nodeType
== 9 /* document */) {
3915 $linkNode
.eq(i
).data('$scope', scope
);
3918 safeAddClass($linkNode
, 'ng-scope');
3919 if (cloneConnectFn
) cloneConnectFn($linkNode
, scope
);
3920 if (compositeLinkFn
) compositeLinkFn(scope
, $linkNode
, $linkNode
);
3925 function wrongMode(localName
, mode
) {
3926 throw Error("Unsupported '" + mode
+ "' for '" + localName
+ "'.");
3929 function safeAddClass($element
, className
) {
3931 $element
.addClass(className
);
3933 // ignore, since it means that we are trying to set class on
3934 // SVG element, where class name is read-only.
3939 * Compile function matches each node in nodeList against the directives. Once all directives
3940 * for a particular node are collected their compile functions are executed. The compile
3941 * functions return values - the linking functions - are combined into a composite linking
3942 * function, which is the a linking function for the node.
3944 * @param {NodeList} nodeList an array of nodes or NodeList to compile
3945 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
3946 * scope argument is auto-generated to the new child of the transcluded parent scope.
3947 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
3948 * rootElement must be set the jqLite collection of the compile root. This is
3949 * needed so that the jqLite collection items can be replaced with widgets.
3950 * @param {number=} max directive priority
3951 * @returns {?function} A composite linking function of all of the matched directives or null.
3953 function compileNodes(nodeList
, transcludeFn
, $rootElement
, maxPriority
) {
3955 nodeLinkFn
, childLinkFn
, directives
, attrs
, linkFnFound
;
3957 for(var i
= 0; i
< nodeList
.length
; i
++) {
3958 attrs
= new Attributes();
3960 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
3961 directives
= collectDirectives(nodeList
[i
], [], attrs
, maxPriority
);
3963 nodeLinkFn
= (directives
.length
)
3964 ? applyDirectivesToNode(directives
, nodeList
[i
], attrs
, transcludeFn
, $rootElement
)
3967 childLinkFn
= (nodeLinkFn
&& nodeLinkFn
.terminal
|| !nodeList
[i
].childNodes
|| !nodeList
[i
].childNodes
.length
)
3969 : compileNodes(nodeList
[i
].childNodes
,
3970 nodeLinkFn
? nodeLinkFn
.transclude : transcludeFn
);
3972 linkFns
.push(nodeLinkFn
);
3973 linkFns
.push(childLinkFn
);
3974 linkFnFound
= (linkFnFound
|| nodeLinkFn
|| childLinkFn
);
3977 // return a linking function if we have found anything, null otherwise
3978 return linkFnFound
? compositeLinkFn : null;
3980 function compositeLinkFn(scope
, nodeList
, $rootElement
, boundTranscludeFn
) {
3981 var nodeLinkFn
, childLinkFn
, node
, childScope
, childTranscludeFn
, i
, ii
, n
;
3983 // copy nodeList so that linking doesn't break due to live list updates.
3984 var stableNodeList
= [];
3985 for (i
= 0, ii
= nodeList
.length
; i
< ii
; i
++) {
3986 stableNodeList
.push(nodeList
[i
]);
3989 for(i
= 0, n
= 0, ii
= linkFns
.length
; i
< ii
; n
++) {
3990 node
= stableNodeList
[n
];
3991 nodeLinkFn
= linkFns
[i
++];
3992 childLinkFn
= linkFns
[i
++];
3995 if (nodeLinkFn
.scope
) {
3996 childScope
= scope
.$new(isObject(nodeLinkFn
.scope
));
3997 jqLite(node
).data('$scope', childScope
);
4001 childTranscludeFn
= nodeLinkFn
.transclude
;
4002 if (childTranscludeFn
|| (!boundTranscludeFn
&& transcludeFn
)) {
4003 nodeLinkFn(childLinkFn
, childScope
, node
, $rootElement
,
4004 (function(transcludeFn
) {
4005 return function(cloneFn
) {
4006 var transcludeScope
= scope
.$new();
4007 transcludeScope
.$$transcluded
= true;
4009 return transcludeFn(transcludeScope
, cloneFn
).
4010 bind('$destroy', bind(transcludeScope
, transcludeScope
.$destroy
));
4012 })(childTranscludeFn
|| transcludeFn
)
4015 nodeLinkFn(childLinkFn
, childScope
, node
, undefined, boundTranscludeFn
);
4017 } else if (childLinkFn
) {
4018 childLinkFn(scope
, node
.childNodes
, undefined, boundTranscludeFn
);
4026 * Looks for directives on the given node and adds them to the directive collection which is
4029 * @param node Node to search.
4030 * @param directives An array to which the directives are added to. This array is sorted before
4031 * the function returns.
4032 * @param attrs The shared attrs object which is used to populate the normalized attributes.
4033 * @param {number=} maxPriority Max directive priority.
4035 function collectDirectives(node
, directives
, attrs
, maxPriority
) {
4036 var nodeType
= node
.nodeType
,
4037 attrsMap
= attrs
.$attr
,
4042 case 1: /* Element */
4043 // use the node name: <directive>
4044 addDirective(directives
,
4045 directiveNormalize(nodeName_(node
).toLowerCase()), 'E', maxPriority
);
4047 // iterate over the attributes
4048 for (var attr
, name
, nName
, value
, nAttrs
= node
.attributes
,
4049 j
= 0, jj
= nAttrs
&& nAttrs
.length
; j
< jj
; j
++) {
4051 if (attr
.specified
) {
4053 nName
= directiveNormalize(name
.toLowerCase());
4054 attrsMap
[nName
] = name
;
4055 attrs
[nName
] = value
= trim((msie
&& name
== 'href')
4056 ? decodeURIComponent(node
.getAttribute(name
, 2))
4058 if (getBooleanAttrName(node
, nName
)) {
4059 attrs
[nName
] = true; // presence means true
4061 addAttrInterpolateDirective(node
, directives
, value
, nName
);
4062 addDirective(directives
, nName
, 'A', maxPriority
);
4066 // use class as directive
4067 className
= node
.className
;
4068 if (isString(className
) && className
!== '') {
4069 while (match
= CLASS_DIRECTIVE_REGEXP
.exec(className
)) {
4070 nName
= directiveNormalize(match
[2]);
4071 if (addDirective(directives
, nName
, 'C', maxPriority
)) {
4072 attrs
[nName
] = trim(match
[3]);
4074 className
= className
.substr(match
.index
+ match
[0].length
);
4078 case 3: /* Text Node */
4079 addTextInterpolateDirective(directives
, node
.nodeValue
);
4081 case 8: /* Comment */
4083 match
= COMMENT_DIRECTIVE_REGEXP
.exec(node
.nodeValue
);
4085 nName
= directiveNormalize(match
[1]);
4086 if (addDirective(directives
, nName
, 'M', maxPriority
)) {
4087 attrs
[nName
] = trim(match
[2]);
4091 // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
4092 // Just ignore it and continue. (Can't seem to reproduce in test case.)
4097 directives
.sort(byPriority
);
4103 * Once the directives have been collected, their compile functions are executed. This method
4104 * is responsible for inlining directive templates as well as terminating the application
4105 * of the directives if the terminal directive has been reached.
4107 * @param {Array} directives Array of collected directives to execute their compile function.
4108 * this needs to be pre-sorted by priority order.
4109 * @param {Node} compileNode The raw DOM node to apply the compile functions to
4110 * @param {Object} templateAttrs The shared attribute function
4111 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
4112 * scope argument is auto-generated to the new child of the transcluded parent scope.
4113 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
4114 * argument has the root jqLite array so that we can replace nodes on it.
4117 function applyDirectivesToNode(directives
, compileNode
, templateAttrs
, transcludeFn
, jqCollection
) {
4118 var terminalPriority
= -Number
.MAX_VALUE
,
4121 newScopeDirective
= null,
4122 newIsolateScopeDirective
= null,
4123 templateDirective
= null,
4124 $compileNode
= templateAttrs
.$$element
= jqLite(compileNode
),
4128 transcludeDirective
,
4129 childTranscludeFn
= transcludeFn
,
4130 controllerDirectives
,
4134 // executes all directives on the current element
4135 for(var i
= 0, ii
= directives
.length
; i
< ii
; i
++) {
4136 directive
= directives
[i
];
4137 $template
= undefined;
4139 if (terminalPriority
> directive
.priority
) {
4140 break; // prevent further processing of directives
4143 if (directiveValue
= directive
.scope
) {
4144 assertNoDuplicate('isolated scope', newIsolateScopeDirective
, directive
, $compileNode
);
4145 if (isObject(directiveValue
)) {
4146 safeAddClass($compileNode
, 'ng-isolate-scope');
4147 newIsolateScopeDirective
= directive
;
4149 safeAddClass($compileNode
, 'ng-scope');
4150 newScopeDirective
= newScopeDirective
|| directive
;
4153 directiveName
= directive
.name
;
4155 if (directiveValue
= directive
.controller
) {
4156 controllerDirectives
= controllerDirectives
|| {};
4157 assertNoDuplicate("'" + directiveName
+ "' controller",
4158 controllerDirectives
[directiveName
], directive
, $compileNode
);
4159 controllerDirectives
[directiveName
] = directive
;
4162 if (directiveValue
= directive
.transclude
) {
4163 assertNoDuplicate('transclusion', transcludeDirective
, directive
, $compileNode
);
4164 transcludeDirective
= directive
;
4165 terminalPriority
= directive
.priority
;
4166 if (directiveValue
== 'element') {
4167 $template
= jqLite(compileNode
);
4168 $compileNode
= templateAttrs
.$$element
=
4169 jqLite(document
.createComment(' ' + directiveName
+ ': ' + templateAttrs
[directiveName
] + ' '));
4170 compileNode
= $compileNode
[0];
4171 replaceWith(jqCollection
, jqLite($template
[0]), compileNode
);
4172 childTranscludeFn
= compile($template
, transcludeFn
, terminalPriority
);
4174 $template
= jqLite(JQLiteClone(compileNode
)).contents();
4175 $compileNode
.html(''); // clear contents
4176 childTranscludeFn
= compile($template
, transcludeFn
);
4180 if ((directiveValue
= directive
.template
)) {
4181 assertNoDuplicate('template', templateDirective
, directive
, $compileNode
);
4182 templateDirective
= directive
;
4183 directiveValue
= denormalizeTemplate(directiveValue
);
4185 if (directive
.replace
) {
4186 $template
= jqLite('<div>' +
4187 trim(directiveValue
) +
4188 '</div>').contents();
4189 compileNode
= $template
[0];
4191 if ($template
.length
!= 1 || compileNode
.nodeType
!== 1) {
4192 throw new Error(MULTI_ROOT_TEMPLATE_ERROR
+ directiveValue
);
4195 replaceWith(jqCollection
, $compileNode
, compileNode
);
4197 var newTemplateAttrs
= {$attr: {}};
4199 // combine directives from the original node and from the template:
4200 // - take the array of directives for this element
4201 // - split it into two parts, those that were already applied and those that weren't
4202 // - collect directives from the template, add them to the second group and sort them
4203 // - append the second group with new directives to the first group
4204 directives
= directives
.concat(
4207 directives
.splice(i
+ 1, directives
.length
- (i
+ 1)),
4211 mergeTemplateAttributes(templateAttrs
, newTemplateAttrs
);
4213 ii
= directives
.length
;
4215 $compileNode
.html(directiveValue
);
4219 if (directive
.templateUrl
) {
4220 assertNoDuplicate('template', templateDirective
, directive
, $compileNode
);
4221 templateDirective
= directive
;
4222 nodeLinkFn
= compileTemplateUrl(directives
.splice(i
, directives
.length
- i
),
4223 nodeLinkFn
, $compileNode
, templateAttrs
, jqCollection
, directive
.replace
,
4225 ii
= directives
.length
;
4226 } else if (directive
.compile
) {
4228 linkFn
= directive
.compile($compileNode
, templateAttrs
, childTranscludeFn
);
4229 if (isFunction(linkFn
)) {
4230 addLinkFns(null, linkFn
);
4231 } else if (linkFn
) {
4232 addLinkFns(linkFn
.pre
, linkFn
.post
);
4235 $exceptionHandler(e
, startingTag($compileNode
));
4239 if (directive
.terminal
) {
4240 nodeLinkFn
.terminal
= true;
4241 terminalPriority
= Math
.max(terminalPriority
, directive
.priority
);
4246 nodeLinkFn
.scope
= newScopeDirective
&& newScopeDirective
.scope
;
4247 nodeLinkFn
.transclude
= transcludeDirective
&& childTranscludeFn
;
4249 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
4252 ////////////////////
4254 function addLinkFns(pre
, post
) {
4256 pre
.require
= directive
.require
;
4257 preLinkFns
.push(pre
);
4260 post
.require
= directive
.require
;
4261 postLinkFns
.push(post
);
4266 function getControllers(require
, $element
) {
4267 var value
, retrievalMethod
= 'data', optional
= false;
4268 if (isString(require
)) {
4269 while((value
= require
.charAt(0)) == '^' || value
== '?') {
4270 require
= require
.substr(1);
4272 retrievalMethod
= 'inheritedData';
4274 optional
= optional
|| value
== '?';
4276 value
= $element
[retrievalMethod
]('$' + require
+ 'Controller');
4277 if (!value
&& !optional
) {
4278 throw Error("No controller: " + require
);
4281 } else if (isArray(require
)) {
4283 forEach(require
, function(require
) {
4284 value
.push(getControllers(require
, $element
));
4291 function nodeLinkFn(childLinkFn
, scope
, linkNode
, $rootElement
, boundTranscludeFn
) {
4292 var attrs
, $element
, i
, ii
, linkFn
, controller
;
4294 if (compileNode
=== linkNode
) {
4295 attrs
= templateAttrs
;
4297 attrs
= shallowCopy(templateAttrs
, new Attributes(jqLite(linkNode
), templateAttrs
.$attr
));
4299 $element
= attrs
.$$element
;
4301 if (newIsolateScopeDirective
) {
4302 var LOCAL_REGEXP
= /^\s*([@=&])\s*(\w*)\s*$/;
4304 var parentScope
= scope
.$parent
|| scope
;
4306 forEach(newIsolateScopeDirective
.scope
, function(definiton
, scopeName
) {
4307 var match
= definiton
.match(LOCAL_REGEXP
) || [],
4308 attrName
= match
[2]|| scopeName
,
4309 mode
= match
[1], // @, =, or &
4311 parentGet
, parentSet
;
4313 scope
.$$isolateBindings
[scopeName
] = mode
+ attrName
;
4318 attrs
.$observe(attrName
, function(value
) {
4319 scope
[scopeName
] = value
;
4321 attrs
.$$observers
[attrName
].$$scope
= parentScope
;
4326 parentGet
= $parse(attrs
[attrName
]);
4327 parentSet
= parentGet
.assign
|| function() {
4328 // reset the change, or we will throw this exception on every $digest
4329 lastValue
= scope
[scopeName
] = parentGet(parentScope
);
4330 throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION
+ attrs
[attrName
] +
4331 ' (directive: ' + newIsolateScopeDirective
.name
+ ')');
4333 lastValue
= scope
[scopeName
] = parentGet(parentScope
);
4334 scope
.$watch(function parentValueWatch() {
4335 var parentValue
= parentGet(parentScope
);
4337 if (parentValue
!== scope
[scopeName
]) {
4338 // we are out of sync and need to copy
4339 if (parentValue
!== lastValue
) {
4340 // parent changed and it has precedence
4341 lastValue
= scope
[scopeName
] = parentValue
;
4343 // if the parent can be assigned then do so
4344 parentSet(parentScope
, parentValue
= lastValue
= scope
[scopeName
]);
4353 parentGet
= $parse(attrs
[attrName
]);
4354 scope
[scopeName
] = function(locals
) {
4355 return parentGet(parentScope
, locals
);
4361 throw Error('Invalid isolate scope definition for directive ' +
4362 newIsolateScopeDirective
.name
+ ': ' + definiton
);
4368 if (controllerDirectives
) {
4369 forEach(controllerDirectives
, function(directive
) {
4374 $transclude: boundTranscludeFn
4377 controller
= directive
.controller
;
4378 if (controller
== '@') {
4379 controller
= attrs
[directive
.name
];
4383 '$' + directive
.name
+ 'Controller',
4384 $controller(controller
, locals
));
4389 for(i
= 0, ii
= preLinkFns
.length
; i
< ii
; i
++) {
4391 linkFn
= preLinkFns
[i
];
4392 linkFn(scope
, $element
, attrs
,
4393 linkFn
.require
&& getControllers(linkFn
.require
, $element
));
4395 $exceptionHandler(e
, startingTag($element
));
4400 childLinkFn
&& childLinkFn(scope
, linkNode
.childNodes
, undefined, boundTranscludeFn
);
4403 for(i
= 0, ii
= postLinkFns
.length
; i
< ii
; i
++) {
4405 linkFn
= postLinkFns
[i
];
4406 linkFn(scope
, $element
, attrs
,
4407 linkFn
.require
&& getControllers(linkFn
.require
, $element
));
4409 $exceptionHandler(e
, startingTag($element
));
4417 * looks up the directive and decorates it with exception handling and proper parameters. We
4418 * call this the boundDirective.
4420 * @param {string} name name of the directive to look up.
4421 * @param {string} location The directive must be found in specific format.
4422 * String containing any of theses characters:
4424 * * `E`: element name
4428 * @returns true if directive was added.
4430 function addDirective(tDirectives
, name
, location
, maxPriority
) {
4432 if (hasDirectives
.hasOwnProperty(name
)) {
4433 for(var directive
, directives
= $injector
.get(name
+ Suffix
),
4434 i
= 0, ii
= directives
.length
; i
<ii
; i
++) {
4436 directive
= directives
[i
];
4437 if ( (maxPriority
=== undefined || maxPriority
> directive
.priority
) &&
4438 directive
.restrict
.indexOf(location
) != -1) {
4439 tDirectives
.push(directive
);
4442 } catch(e
) { $exceptionHandler(e
); }
4450 * When the element is replaced with HTML template then the new attributes
4451 * on the template need to be merged with the existing attributes in the DOM.
4452 * The desired effect is to have both of the attributes present.
4454 * @param {object} dst destination attributes (original DOM)
4455 * @param {object} src source attributes (from the directive template)
4457 function mergeTemplateAttributes(dst
, src
) {
4458 var srcAttr
= src
.$attr
,
4459 dstAttr
= dst
.$attr
,
4460 $element
= dst
.$$element
;
4462 // reapply the old attributes to the new element
4463 forEach(dst
, function(value
, key
) {
4464 if (key
.charAt(0) != '$') {
4466 value
+= (key
=== 'style' ? ';' : ' ') + src
[key
];
4468 dst
.$set(key
, value
, true, srcAttr
[key
]);
4472 // copy the new attributes on the old attrs object
4473 forEach(src
, function(value
, key
) {
4474 if (key
== 'class') {
4475 safeAddClass($element
, value
);
4476 dst
['class'] = (dst
['class'] ? dst
['class'] + ' ' : '') + value
;
4477 } else if (key
== 'style') {
4478 $element
.attr('style', $element
.attr('style') + ';' + value
);
4479 } else if (key
.charAt(0) != '$' && !dst
.hasOwnProperty(key
)) {
4481 dstAttr
[key
] = srcAttr
[key
];
4487 function compileTemplateUrl(directives
, beforeTemplateNodeLinkFn
, $compileNode
, tAttrs
,
4488 $rootElement
, replace
, childTranscludeFn
) {
4490 afterTemplateNodeLinkFn
,
4491 afterTemplateChildLinkFn
,
4492 beforeTemplateCompileNode
= $compileNode
[0],
4493 origAsyncDirective
= directives
.shift(),
4494 // The fact that we have to copy and patch the directive seems wrong!
4495 derivedSyncDirective
= extend({}, origAsyncDirective
, {
4496 controller: null, templateUrl: null, transclude: null, scope: null
4499 $compileNode
.html('');
4501 $http
.get(origAsyncDirective
.templateUrl
, {cache: $templateCache
}).
4502 success(function(content
) {
4503 var compileNode
, tempTemplateAttrs
, $template
;
4505 content
= denormalizeTemplate(content
);
4508 $template
= jqLite('<div>' + trim(content
) + '</div>').contents();
4509 compileNode
= $template
[0];
4511 if ($template
.length
!= 1 || compileNode
.nodeType
!== 1) {
4512 throw new Error(MULTI_ROOT_TEMPLATE_ERROR
+ content
);
4515 tempTemplateAttrs
= {$attr: {}};
4516 replaceWith($rootElement
, $compileNode
, compileNode
);
4517 collectDirectives(compileNode
, directives
, tempTemplateAttrs
);
4518 mergeTemplateAttributes(tAttrs
, tempTemplateAttrs
);
4520 compileNode
= beforeTemplateCompileNode
;
4521 $compileNode
.html(content
);
4524 directives
.unshift(derivedSyncDirective
);
4525 afterTemplateNodeLinkFn
= applyDirectivesToNode(directives
, compileNode
, tAttrs
, childTranscludeFn
);
4526 afterTemplateChildLinkFn
= compileNodes($compileNode
[0].childNodes
, childTranscludeFn
);
4529 while(linkQueue
.length
) {
4530 var controller
= linkQueue
.pop(),
4531 linkRootElement
= linkQueue
.pop(),
4532 beforeTemplateLinkNode
= linkQueue
.pop(),
4533 scope
= linkQueue
.pop(),
4534 linkNode
= compileNode
;
4536 if (beforeTemplateLinkNode
!== beforeTemplateCompileNode
) {
4537 // it was cloned therefore we have to clone as well.
4538 linkNode
= JQLiteClone(compileNode
);
4539 replaceWith(linkRootElement
, jqLite(beforeTemplateLinkNode
), linkNode
);
4542 afterTemplateNodeLinkFn(function() {
4543 beforeTemplateNodeLinkFn(afterTemplateChildLinkFn
, scope
, linkNode
, $rootElement
, controller
);
4544 }, scope
, linkNode
, $rootElement
, controller
);
4548 error(function(response
, code
, headers
, config
) {
4549 throw Error('Failed to load template: ' + config
.url
);
4552 return function delayedNodeLinkFn(ignoreChildLinkFn
, scope
, node
, rootElement
, controller
) {
4554 linkQueue
.push(scope
);
4555 linkQueue
.push(node
);
4556 linkQueue
.push(rootElement
);
4557 linkQueue
.push(controller
);
4559 afterTemplateNodeLinkFn(function() {
4560 beforeTemplateNodeLinkFn(afterTemplateChildLinkFn
, scope
, node
, rootElement
, controller
);
4561 }, scope
, node
, rootElement
, controller
);
4568 * Sorting function for bound directives.
4570 function byPriority(a
, b
) {
4571 return b
.priority
- a
.priority
;
4575 function assertNoDuplicate(what
, previousDirective
, directive
, element
) {
4576 if (previousDirective
) {
4577 throw Error('Multiple directives [' + previousDirective
.name
+ ', ' +
4578 directive
.name
+ '] asking for ' + what
+ ' on: ' + startingTag(element
));
4583 function addTextInterpolateDirective(directives
, text
) {
4584 var interpolateFn
= $interpolate(text
, true);
4585 if (interpolateFn
) {
4588 compile: valueFn(function textInterpolateLinkFn(scope
, node
) {
4589 var parent
= node
.parent(),
4590 bindings
= parent
.data('$binding') || [];
4591 bindings
.push(interpolateFn
);
4592 safeAddClass(parent
.data('$binding', bindings
), 'ng-binding');
4593 scope
.$watch(interpolateFn
, function interpolateFnWatchAction(value
) {
4594 node
[0].nodeValue
= value
;
4602 function addAttrInterpolateDirective(node
, directives
, value
, name
) {
4603 var interpolateFn
= $interpolate(value
, true);
4605 // no interpolation found -> ignore
4606 if (!interpolateFn
) return;
4611 compile: valueFn(function attrInterpolateLinkFn(scope
, element
, attr
) {
4612 var $$observers
= (attr
.$$observers
|| (attr
.$$observers
= {}));
4614 if (name
=== 'class') {
4615 // we need to interpolate classes again, in the case the element was replaced
4616 // and therefore the two class attrs got merged - we want to interpolate the result
4617 interpolateFn
= $interpolate(attr
[name
], true);
4620 attr
[name
] = undefined;
4621 ($$observers
[name
] || ($$observers
[name
] = [])).$$inter
= true;
4622 (attr
.$$observers
&& attr
.$$observers
[name
].$$scope
|| scope
).
4623 $watch(interpolateFn
, function interpolateFnWatchAction(value
) {
4624 attr
.$set(name
, value
);
4632 * This is a special jqLite.replaceWith, which can replace items which
4633 * have no parents, provided that the containing jqLite collection is provided.
4635 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
4636 * in the root of the tree.
4637 * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
4638 * but replace its DOM node reference.
4639 * @param {Node} newNode The new DOM node.
4641 function replaceWith($rootElement
, $element
, newNode
) {
4642 var oldNode
= $element
[0],
4643 parent
= oldNode
.parentNode
,
4647 for(i
= 0, ii
= $rootElement
.length
; i
< ii
; i
++) {
4648 if ($rootElement
[i
] == oldNode
) {
4649 $rootElement
[i
] = newNode
;
4656 parent
.replaceChild(newNode
, oldNode
);
4659 newNode
[jqLite
.expando
] = oldNode
[jqLite
.expando
];
4660 $element
[0] = newNode
;
4665 var PREFIX_REGEXP
= /^(x[\:\-_]|data[\:\-_])/i;
4667 * Converts all accepted directives format into proper directive name.
4668 * All of these will become 'myDirective':
4674 * Also there is special case for Moz prefix starting with upper case letter.
4675 * @param name Name to normalize
4677 function directiveNormalize(name
) {
4678 return camelCase(name
.replace(PREFIX_REGEXP
, ''));
4683 * @name ng.$compile.directive.Attributes
4686 * A shared object between directive compile / linking functions which contains normalized DOM element
4687 * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
4688 * since all of these are treated as equivalent in Angular:
4690 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
4695 * @name ng.$compile.directive.Attributes#$attr
4696 * @propertyOf ng.$compile.directive.Attributes
4697 * @returns {object} A map of DOM element attribute names to the normalized name. This is
4698 * needed to do reverse lookup from normalized name back to actual name.
4704 * @name ng.$compile.directive.Attributes#$set
4705 * @methodOf ng.$compile.directive.Attributes
4709 * Set DOM element attribute value.
4712 * @param {string} name Normalized element attribute name of the property to modify. The name is
4713 * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
4714 * property to the original name.
4715 * @param {string} value Value to set the attribute to.
4721 * Closure compiler type information
4724 function nodesetLinkingFn(
4725 /* angular.Scope */ scope
,
4726 /* NodeList */ nodeList
,
4727 /* Element */ rootElement
,
4728 /* function(Function) */ boundTranscludeFn
4731 function directiveLinkingFn(
4732 /* nodesetLinkingFn */ nodesetLinkingFn
,
4733 /* angular.Scope */ scope
,
4735 /* Element */ rootElement
,
4736 /* function(Function) */ boundTranscludeFn
4741 * @name ng.$controllerProvider
4743 * The {@link ng.$controller $controller service} is used by Angular to create new
4746 * This provider allows controller registration via the
4747 * {@link ng.$controllerProvider#register register} method.
4749 function $ControllerProvider() {
4750 var controllers
= {};
4755 * @name ng.$controllerProvider#register
4756 * @methodOf ng.$controllerProvider
4757 * @param {string} name Controller name
4758 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
4759 * annotations in the array notation).
4761 this.register = function(name
, constructor) {
4762 if (isObject(name
)) {
4763 extend(controllers
, name
)
4765 controllers
[name
] = constructor;
4770 this.$get = ['$injector', '$window', function($injector
, $window
) {
4774 * @name ng.$controller
4775 * @requires $injector
4777 * @param {Function|string} constructor If called with a function then it's considered to be the
4778 * controller constructor function. Otherwise it's considered to be a string which is used
4779 * to retrieve the controller constructor using the following steps:
4781 * * check if a controller with given name is registered via `$controllerProvider`
4782 * * check if evaluating the string on the current scope returns a constructor
4783 * * check `window[constructor]` on the global `window` object
4785 * @param {Object} locals Injection locals for Controller.
4786 * @return {Object} Instance of given controller.
4789 * `$controller` service is responsible for instantiating controllers.
4791 * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into
4792 * a service, so that one can override this service with {@link https://gist.github.com/1649788
4795 return function(constructor, locals
) {
4796 if(isString(constructor)) {
4797 var name
= constructor;
4798 constructor = controllers
.hasOwnProperty(name
)
4800 : getter(locals
.$scope
, name
, true) || getter($window
, name
, true);
4802 assertArgFn(constructor, name
, true);
4805 return $injector
.instantiate(constructor, locals
);
4812 * @name ng.$document
4816 * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
4819 function $DocumentProvider(){
4820 this.$get = ['$window', function(window
){
4821 return jqLite(window
.document
);
4827 * @name ng.$exceptionHandler
4831 * Any uncaught exception in angular expressions is delegated to this service.
4832 * The default implementation simply delegates to `$log.error` which logs it into
4833 * the browser console.
4835 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
4836 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
4838 * @param {Error} exception Exception associated with the error.
4839 * @param {string=} cause optional information about the context in which
4840 * the error was thrown.
4843 function $ExceptionHandlerProvider() {
4844 this.$get = ['$log', function($log
) {
4845 return function(exception
, cause
) {
4846 $log
.error
.apply($log
, arguments
);
4853 * @name ng.$interpolateProvider
4858 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
4860 function $InterpolateProvider() {
4861 var startSymbol
= '{{';
4862 var endSymbol
= '}}';
4866 * @name ng.$interpolateProvider#startSymbol
4867 * @methodOf ng.$interpolateProvider
4869 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
4871 * @param {string=} value new value to set the starting symbol to.
4872 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
4874 this.startSymbol = function(value
){
4876 startSymbol
= value
;
4885 * @name ng.$interpolateProvider#endSymbol
4886 * @methodOf ng.$interpolateProvider
4888 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
4890 * @param {string=} value new value to set the ending symbol to.
4891 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
4893 this.endSymbol = function(value
){
4903 this.$get = ['$parse', function($parse
) {
4904 var startSymbolLength
= startSymbol
.length
,
4905 endSymbolLength
= endSymbol
.length
;
4909 * @name ng.$interpolate
4916 * Compiles a string with markup into an interpolation function. This service is used by the
4917 * HTML {@link ng.$compile $compile} service for data binding. See
4918 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
4919 * interpolation markup.
4923 var $interpolate = ...; // injected
4924 var exp = $interpolate('Hello {{name}}!');
4925 expect(exp({name:'Angular'}).toEqual('Hello Angular!');
4929 * @param {string} text The text with markup to interpolate.
4930 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
4931 * embedded expression in order to return an interpolation function. Strings with no
4932 * embedded expression will return null for the interpolation function.
4933 * @returns {function(context)} an interpolation function which is used to compute the interpolated
4934 * string. The function has these parameters:
4936 * * `context`: an object against which any expressions embedded in the strings are evaluated
4940 function $interpolate(text
, mustHaveExpression
) {
4945 length
= text
.length
,
4946 hasInterpolation
= false,
4951 while(index
< length
) {
4952 if ( ((startIndex
= text
.indexOf(startSymbol
, index
)) != -1) &&
4953 ((endIndex
= text
.indexOf(endSymbol
, startIndex
+ startSymbolLength
)) != -1) ) {
4954 (index
!= startIndex
) && parts
.push(text
.substring(index
, startIndex
));
4955 parts
.push(fn
= $parse(exp
= text
.substring(startIndex
+ startSymbolLength
, endIndex
)));
4957 index
= endIndex
+ endSymbolLength
;
4958 hasInterpolation
= true;
4960 // we did not find anything, so we have to add the remainder to the parts array
4961 (index
!= length
) && parts
.push(text
.substring(index
));
4966 if (!(length
= parts
.length
)) {
4967 // we added, nothing, must have been an empty string.
4972 if (!mustHaveExpression
|| hasInterpolation
) {
4973 concat
.length
= length
;
4974 fn = function(context
) {
4975 for(var i
= 0, ii
= length
, part
; i
<ii
; i
++) {
4976 if (typeof (part
= parts
[i
]) == 'function') {
4977 part
= part(context
);
4978 if (part
== null || part
== undefined) {
4980 } else if (typeof part
!= 'string') {
4981 part
= toJson(part
);
4986 return concat
.join('');
4997 * @name ng.$interpolate#startSymbol
4998 * @methodOf ng.$interpolate
5000 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
5002 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
5005 * @returns {string} start symbol.
5007 $interpolate
.startSymbol = function() {
5014 * @name ng.$interpolate#endSymbol
5015 * @methodOf ng.$interpolate
5017 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
5019 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
5022 * @returns {string} start symbol.
5024 $interpolate
.endSymbol = function() {
5028 return $interpolate
;
5032 var URL_MATCH
= /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
5033 PATH_MATCH
= /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
5034 HASH_MATCH
= PATH_MATCH
,
5035 DEFAULT_PORTS
= {'http': 80, 'https': 443, 'ftp': 21};
5039 * Encode path using encodeUriSegment, ignoring forward slashes
5041 * @param {string} path Path to encode
5044 function encodePath(path
) {
5045 var segments
= path
.split('/'),
5046 i
= segments
.length
;
5049 segments
[i
] = encodeUriSegment(segments
[i
]);
5052 return segments
.join('/');
5055 function stripHash(url
) {
5056 return url
.split('#')[0];
5060 function matchUrl(url
, obj
) {
5061 var match
= URL_MATCH
.exec(url
);
5066 port: int(match
[5]) || DEFAULT_PORTS
[match
[1]] || null,
5067 path: match
[6] || '/',
5073 obj
.$$protocol
= match
.protocol
;
5074 obj
.$$host
= match
.host
;
5075 obj
.$$port
= match
.port
;
5082 function composeProtocolHostPort(protocol
, host
, port
) {
5083 return protocol
+ '://' + host
+ (port
== DEFAULT_PORTS
[protocol
] ? '' : ':' + port
);
5087 function pathPrefixFromBase(basePath
) {
5088 return basePath
.substr(0, basePath
.lastIndexOf('/'));
5092 function convertToHtml5Url(url
, basePath
, hashPrefix
) {
5093 var match
= matchUrl(url
);
5095 // already html5 url
5096 if (decodeURIComponent(match
.path
) != basePath
|| isUndefined(match
.hash
) ||
5097 match
.hash
.indexOf(hashPrefix
) !== 0) {
5099 // convert hashbang url -> html5 url
5101 return composeProtocolHostPort(match
.protocol
, match
.host
, match
.port
) +
5102 pathPrefixFromBase(basePath
) + match
.hash
.substr(hashPrefix
.length
);
5107 function convertToHashbangUrl(url
, basePath
, hashPrefix
) {
5108 var match
= matchUrl(url
);
5110 // already hashbang url
5111 if (decodeURIComponent(match
.path
) == basePath
&& !isUndefined(match
.hash
) &&
5112 match
.hash
.indexOf(hashPrefix
) === 0) {
5114 // convert html5 url -> hashbang url
5116 var search
= match
.search
&& '?' + match
.search
|| '',
5117 hash
= match
.hash
&& '#' + match
.hash
|| '',
5118 pathPrefix
= pathPrefixFromBase(basePath
),
5119 path
= match
.path
.substr(pathPrefix
.length
);
5121 if (match
.path
.indexOf(pathPrefix
) !== 0) {
5122 throw Error('Invalid url "' + url
+ '", missing path prefix "' + pathPrefix
+ '" !');
5125 return composeProtocolHostPort(match
.protocol
, match
.host
, match
.port
) + basePath
+
5126 '#' + hashPrefix
+ path
+ search
+ hash
;
5132 * LocationUrl represents an url
5133 * This object is exposed as $location service when HTML5 mode is enabled and supported
5136 * @param {string} url HTML5 url
5137 * @param {string} pathPrefix
5139 function LocationUrl(url
, pathPrefix
, appBaseUrl
) {
5140 pathPrefix
= pathPrefix
|| '';
5143 * Parse given html5 (regular) url string into properties
5144 * @param {string} newAbsoluteUrl HTML5 url
5147 this.$$parse = function(newAbsoluteUrl
) {
5148 var match
= matchUrl(newAbsoluteUrl
, this);
5150 if (match
.path
.indexOf(pathPrefix
) !== 0) {
5151 throw Error('Invalid url "' + newAbsoluteUrl
+ '", missing path prefix "' + pathPrefix
+ '" !');
5154 this.$$path
= decodeURIComponent(match
.path
.substr(pathPrefix
.length
));
5155 this.$$search
= parseKeyValue(match
.search
);
5156 this.$$hash
= match
.hash
&& decodeURIComponent(match
.hash
) || '';
5162 * Compose url and update `absUrl` property
5165 this.$$compose = function() {
5166 var search
= toKeyValue(this.$$search
),
5167 hash
= this.$$hash
? '#' + encodeUriSegment(this.$$hash
) : '';
5169 this.$$url
= encodePath(this.$$path
) + (search
? '?' + search : '') + hash
;
5170 this.$$absUrl
= composeProtocolHostPort(this.$$protocol
, this.$$host
, this.$$port
) +
5171 pathPrefix
+ this.$$url
;
5175 this.$$rewriteAppUrl = function(absoluteLinkUrl
) {
5176 if(absoluteLinkUrl
.indexOf(appBaseUrl
) == 0) {
5177 return absoluteLinkUrl
;
5187 * LocationHashbangUrl represents url
5188 * This object is exposed as $location service when html5 history api is disabled or not supported
5191 * @param {string} url Legacy url
5192 * @param {string} hashPrefix Prefix for hash part (containing path and search)
5194 function LocationHashbangUrl(url
, hashPrefix
, appBaseUrl
) {
5198 * Parse given hashbang url into properties
5199 * @param {string} url Hashbang url
5202 this.$$parse = function(url
) {
5203 var match
= matchUrl(url
, this);
5206 if (match
.hash
&& match
.hash
.indexOf(hashPrefix
) !== 0) {
5207 throw Error('Invalid url "' + url
+ '", missing hash prefix "' + hashPrefix
+ '" !');
5210 basePath
= match
.path
+ (match
.search
? '?' + match
.search : '');
5211 match
= HASH_MATCH
.exec((match
.hash
|| '').substr(hashPrefix
.length
));
5213 this.$$path
= (match
[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match
[1]);
5218 this.$$search
= parseKeyValue(match
[3]);
5219 this.$$hash
= match
[5] && decodeURIComponent(match
[5]) || '';
5225 * Compose hashbang url and update `absUrl` property
5228 this.$$compose = function() {
5229 var search
= toKeyValue(this.$$search
),
5230 hash
= this.$$hash
? '#' + encodeUriSegment(this.$$hash
) : '';
5232 this.$$url
= encodePath(this.$$path
) + (search
? '?' + search : '') + hash
;
5233 this.$$absUrl
= composeProtocolHostPort(this.$$protocol
, this.$$host
, this.$$port
) +
5234 basePath
+ (this.$$url
? '#' + hashPrefix
+ this.$$url : '');
5237 this.$$rewriteAppUrl = function(absoluteLinkUrl
) {
5238 if(absoluteLinkUrl
.indexOf(appBaseUrl
) == 0) {
5239 return absoluteLinkUrl
;
5248 LocationUrl
.prototype = {
5251 * Has any change been replacing ?
5258 * @name ng.$location#absUrl
5259 * @methodOf ng.$location
5262 * This method is getter only.
5264 * Return full url representation with all segments encoded according to rules specified in
5265 * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
5267 * @return {string} full url
5269 absUrl: locationGetter('$$absUrl'),
5273 * @name ng.$location#url
5274 * @methodOf ng.$location
5277 * This method is getter / setter.
5279 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
5281 * Change path, search and hash, when called with parameter and return `$location`.
5283 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
5284 * @return {string} url
5286 url: function(url
, replace
) {
5287 if (isUndefined(url
))
5290 var match
= PATH_MATCH
.exec(url
);
5291 if (match
[1]) this.path(decodeURIComponent(match
[1]));
5292 if (match
[2] || match
[1]) this.search(match
[3] || '');
5293 this.hash(match
[5] || '', replace
);
5300 * @name ng.$location#protocol
5301 * @methodOf ng.$location
5304 * This method is getter only.
5306 * Return protocol of current url.
5308 * @return {string} protocol of current url
5310 protocol: locationGetter('$$protocol'),
5314 * @name ng.$location#host
5315 * @methodOf ng.$location
5318 * This method is getter only.
5320 * Return host of current url.
5322 * @return {string} host of current url.
5324 host: locationGetter('$$host'),
5328 * @name ng.$location#port
5329 * @methodOf ng.$location
5332 * This method is getter only.
5334 * Return port of current url.
5336 * @return {Number} port
5338 port: locationGetter('$$port'),
5342 * @name ng.$location#path
5343 * @methodOf ng.$location
5346 * This method is getter / setter.
5348 * Return path of current url when called without any parameter.
5350 * Change path when called with parameter and return `$location`.
5352 * Note: Path should always begin with forward slash (/), this method will add the forward slash
5355 * @param {string=} path New path
5356 * @return {string} path
5358 path: locationGetterSetter('$$path', function(path
) {
5359 return path
.charAt(0) == '/' ? path : '/' + path
;
5364 * @name ng.$location#search
5365 * @methodOf ng.$location
5368 * This method is getter / setter.
5370 * Return search part (as object) of current url when called without any parameter.
5372 * Change search part when called with parameter and return `$location`.
5374 * @param {string|object<string,string>=} search New search params - string or hash object
5375 * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
5376 * single search parameter. If the value is `null`, the parameter will be deleted.
5378 * @return {string} search
5380 search: function(search
, paramValue
) {
5381 if (isUndefined(search
))
5382 return this.$$search
;
5384 if (isDefined(paramValue
)) {
5385 if (paramValue
=== null) {
5386 delete this.$$search
[search
];
5388 this.$$search
[search
] = paramValue
;
5391 this.$$search
= isString(search
) ? parseKeyValue(search
) : search
;
5400 * @name ng.$location#hash
5401 * @methodOf ng.$location
5404 * This method is getter / setter.
5406 * Return hash fragment when called without any parameter.
5408 * Change hash fragment when called with parameter and return `$location`.
5410 * @param {string=} hash New hash fragment
5411 * @return {string} hash
5413 hash: locationGetterSetter('$$hash', identity
),
5417 * @name ng.$location#replace
5418 * @methodOf ng.$location
5421 * If called, all changes to $location during current `$digest` will be replacing current history
5422 * record, instead of adding new one.
5424 replace: function() {
5425 this.$$replace
= true;
5430 LocationHashbangUrl
.prototype = inherit(LocationUrl
.prototype);
5432 function LocationHashbangInHtml5Url(url
, hashPrefix
, appBaseUrl
, baseExtra
) {
5433 LocationHashbangUrl
.apply(this, arguments
);
5436 this.$$rewriteAppUrl = function(absoluteLinkUrl
) {
5437 if (absoluteLinkUrl
.indexOf(appBaseUrl
) == 0) {
5438 return appBaseUrl
+ baseExtra
+ '#' + hashPrefix
+ absoluteLinkUrl
.substr(appBaseUrl
.length
);
5443 LocationHashbangInHtml5Url
.prototype = inherit(LocationHashbangUrl
.prototype);
5445 function locationGetter(property
) {
5447 return this[property
];
5452 function locationGetterSetter(property
, preprocess
) {
5453 return function(value
) {
5454 if (isUndefined(value
))
5455 return this[property
];
5457 this[property
] = preprocess(value
);
5467 * @name ng.$location
5469 * @requires $browser
5470 * @requires $sniffer
5471 * @requires $rootElement
5474 * The $location service parses the URL in the browser address bar (based on the
5475 * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
5476 * available to your application. Changes to the URL in the address bar are reflected into
5477 * $location service and changes to $location are reflected into the browser address bar.
5479 * **The $location service:**
5481 * - Exposes the current URL in the browser address bar, so you can
5482 * - Watch and observe the URL.
5484 * - Synchronizes the URL with the browser when the user
5485 * - Changes the address bar.
5486 * - Clicks the back or forward button (or clicks a History link).
5487 * - Clicks on a link.
5488 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
5490 * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
5491 * Services: Using $location}
5496 * @name ng.$locationProvider
5498 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
5500 function $LocationProvider(){
5501 var hashPrefix
= '',
5506 * @name ng.$locationProvider#hashPrefix
5507 * @methodOf ng.$locationProvider
5509 * @param {string=} prefix Prefix for hash part (containing path and search)
5510 * @returns {*} current value if used as getter or itself (chaining) if used as setter
5512 this.hashPrefix = function(prefix
) {
5513 if (isDefined(prefix
)) {
5514 hashPrefix
= prefix
;
5523 * @name ng.$locationProvider#html5Mode
5524 * @methodOf ng.$locationProvider
5526 * @param {string=} mode Use HTML5 strategy if available.
5527 * @returns {*} current value if used as getter or itself (chaining) if used as setter
5529 this.html5Mode = function(mode
) {
5530 if (isDefined(mode
)) {
5538 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
5539 function( $rootScope
, $browser
, $sniffer
, $rootElement
) {
5543 initUrl
= $browser
.url(),
5544 initUrlParts
= matchUrl(initUrl
),
5548 basePath
= $browser
.baseHref() || '/';
5549 pathPrefix
= pathPrefixFromBase(basePath
);
5551 composeProtocolHostPort(initUrlParts
.protocol
, initUrlParts
.host
, initUrlParts
.port
) +
5554 if ($sniffer
.history
) {
5555 $location
= new LocationUrl(
5556 convertToHtml5Url(initUrl
, basePath
, hashPrefix
),
5557 pathPrefix
, appBaseUrl
);
5559 $location
= new LocationHashbangInHtml5Url(
5560 convertToHashbangUrl(initUrl
, basePath
, hashPrefix
),
5561 hashPrefix
, appBaseUrl
, basePath
.substr(pathPrefix
.length
+ 1));
5565 composeProtocolHostPort(initUrlParts
.protocol
, initUrlParts
.host
, initUrlParts
.port
) +
5566 (initUrlParts
.path
|| '') +
5567 (initUrlParts
.search
? ('?' + initUrlParts
.search
) : '') +
5568 '#' + hashPrefix
+ '/';
5570 $location
= new LocationHashbangUrl(initUrl
, hashPrefix
, appBaseUrl
);
5573 $rootElement
.bind('click', function(event
) {
5574 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
5575 // currently we open nice url link and redirect then
5577 if (event
.ctrlKey
|| event
.metaKey
|| event
.which
== 2) return;
5579 var elm
= jqLite(event
.target
);
5581 // traverse the DOM up to find first A tag
5582 while (lowercase(elm
[0].nodeName
) !== 'a') {
5583 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
5584 if (elm
[0] === $rootElement
[0] || !(elm
= elm
.parent())[0]) return;
5587 var absHref
= elm
.prop('href'),
5588 rewrittenUrl
= $location
.$$rewriteAppUrl(absHref
);
5590 if (absHref
&& !elm
.attr('target') && rewrittenUrl
) {
5591 // update location manually
5592 $location
.$$parse(rewrittenUrl
);
5593 $rootScope
.$apply();
5594 event
.preventDefault();
5595 // hack to work around FF6 bug 684208 when scenario runner clicks on links
5596 window
.angular
['ff-684208-preventDefault'] = true;
5601 // rewrite hashbang url <> html5 url
5602 if ($location
.absUrl() != initUrl
) {
5603 $browser
.url($location
.absUrl(), true);
5606 // update $location when $browser url changes
5607 $browser
.onUrlChange(function(newUrl
) {
5608 if ($location
.absUrl() != newUrl
) {
5609 if ($rootScope
.$broadcast('$locationChangeStart', newUrl
, $location
.absUrl()).defaultPrevented
) {
5610 $browser
.url($location
.absUrl());
5613 $rootScope
.$evalAsync(function() {
5614 var oldUrl
= $location
.absUrl();
5616 $location
.$$parse(newUrl
);
5617 afterLocationChange(oldUrl
);
5619 if (!$rootScope
.$$phase
) $rootScope
.$digest();
5624 var changeCounter
= 0;
5625 $rootScope
.$watch(function $locationWatch() {
5626 var oldUrl
= $browser
.url();
5627 var currentReplace
= $location
.$$replace
;
5629 if (!changeCounter
|| oldUrl
!= $location
.absUrl()) {
5631 $rootScope
.$evalAsync(function() {
5632 if ($rootScope
.$broadcast('$locationChangeStart', $location
.absUrl(), oldUrl
).
5634 $location
.$$parse(oldUrl
);
5636 $browser
.url($location
.absUrl(), currentReplace
);
5637 afterLocationChange(oldUrl
);
5641 $location
.$$replace
= false;
5643 return changeCounter
;
5648 function afterLocationChange(oldUrl
) {
5649 $rootScope
.$broadcast('$locationChangeSuccess', $location
.absUrl(), oldUrl
);
5660 * Simple service for logging. Default implementation writes the message
5661 * into the browser's console (if present).
5663 * The main purpose of this service is to simplify debugging and troubleshooting.
5667 <file name="script.js">
5668 function LogCtrl($scope, $log) {
5670 $scope.message = 'Hello World!';
5673 <file name="index.html">
5674 <div ng-controller="LogCtrl">
5675 <p>Reload this page with open console, enter text and hit the log button...</p>
5677 <input type="text" ng-model="message"/>
5678 <button ng-click="$log.log(message)">log</button>
5679 <button ng-click="$log.warn(message)">warn</button>
5680 <button ng-click="$log.info(message)">info</button>
5681 <button ng-click="$log.error(message)">error</button>
5687 function $LogProvider(){
5688 this.$get = ['$window', function($window
){
5696 * Write a log message
5698 log: consoleLog('log'),
5702 * @name ng.$log#warn
5706 * Write a warning message
5708 warn: consoleLog('warn'),
5712 * @name ng.$log#info
5716 * Write an information message
5718 info: consoleLog('info'),
5722 * @name ng.$log#error
5726 * Write an error message
5728 error: consoleLog('error')
5731 function formatError(arg
) {
5732 if (arg
instanceof Error
) {
5734 arg
= (arg
.message
&& arg
.stack
.indexOf(arg
.message
) === -1)
5735 ? 'Error: ' + arg
.message
+ '\n' + arg
.stack
5737 } else if (arg
.sourceURL
) {
5738 arg
= arg
.message
+ '\n' + arg
.sourceURL
+ ':' + arg
.line
;
5744 function consoleLog(type
) {
5745 var console
= $window
.console
|| {},
5746 logFn
= console
[type
] || console
.log
|| noop
;
5751 forEach(arguments
, function(arg
) {
5752 args
.push(formatError(arg
));
5754 return logFn
.apply(console
, args
);
5758 // we are IE which either doesn't have window.console => this is noop and we do nothing,
5759 // or we are IE where console.log doesn't have apply so we log at least first 2 args
5760 return function(arg1
, arg2
) {
5768 'null':function(){return null;},
5769 'true':function(){return true;},
5770 'false':function(){return false;},
5772 '+':function(self
, locals
, a
,b
){
5773 a
=a(self
, locals
); b
=b(self
, locals
);
5780 return isDefined(b
)?b:undefined;},
5781 '-':function(self
, locals
, a
,b
){a
=a(self
, locals
); b
=b(self
, locals
); return (isDefined(a
)?a:0)-(isDefined(b
)?b:0);},
5782 '*':function(self
, locals
, a
,b
){return a(self
, locals
)*b(self
, locals
);},
5783 '/':function(self
, locals
, a
,b
){return a(self
, locals
)/b(self
, locals
);},
5784 '%':function(self
, locals
, a
,b
){return a(self
, locals
)%b(self
, locals
);},
5785 '^':function(self
, locals
, a
,b
){return a(self
, locals
)^b(self
, locals
);},
5787 '==':function(self
, locals
, a
,b
){return a(self
, locals
)==b(self
, locals
);},
5788 '!=':function(self
, locals
, a
,b
){return a(self
, locals
)!=b(self
, locals
);},
5789 '<':function(self
, locals
, a
,b
){return a(self
, locals
)<b(self
, locals
);},
5790 '>':function(self
, locals
, a
,b
){return a(self
, locals
)>b(self
, locals
);},
5791 '<=':function(self
, locals
, a
,b
){return a(self
, locals
)<=b(self
, locals
);},
5792 '>=':function(self
, locals
, a
,b
){return a(self
, locals
)>=b(self
, locals
);},
5793 '&&':function(self
, locals
, a
,b
){return a(self
, locals
)&&b(self
, locals
);},
5794 '||':function(self
, locals
, a
,b
){return a(self
, locals
)||b(self
, locals
);},
5795 '&':function(self
, locals
, a
,b
){return a(self
, locals
)&b(self
, locals
);},
5796 // '|':function(self, locals, a,b){return a|b;},
5797 '|':function(self
, locals
, a
,b
){return b(self
, locals
)(self
, locals
, a(self
, locals
));},
5798 '!':function(self
, locals
, a
){return !a(self
, locals
);}
5800 var ESCAPE
= {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
5802 function lex(text
, csp
){
5808 lastCh
= ':'; // can start regexp
5810 while (index
< text
.length
) {
5811 ch
= text
.charAt(index
);
5814 } else if (isNumber(ch
) || is('.') && isNumber(peek())) {
5816 } else if (isIdent(ch
)) {
5818 // identifiers can only be if the preceding char was a { or ,
5819 if (was('{,') && json
[0]=='{' &&
5820 (token
=tokens
[tokens
.length
-1])) {
5821 token
.json
= token
.text
.indexOf('.') == -1;
5823 } else if (is('(){}[].,;:')) {
5827 json:(was(':[,') && is('{[')) || is('}]:,')
5829 if (is('{[')) json
.unshift(ch
);
5830 if (is('}]')) json
.shift();
5832 } else if (isWhitespace(ch
)) {
5836 var ch2
= ch
+ peek(),
5838 fn2
= OPERATORS
[ch2
];
5840 tokens
.push({index:index
, text:ch2
, fn:fn2
});
5843 tokens
.push({index:index
, text:ch
, fn:fn
, json: was('[,:') && is('+-')});
5846 throwError("Unexpected next character ", index
, index
+1);
5853 function is(chars
) {
5854 return chars
.indexOf(ch
) != -1;
5857 function was(chars
) {
5858 return chars
.indexOf(lastCh
) != -1;
5862 return index
+ 1 < text
.length
? text
.charAt(index
+ 1) : false;
5864 function isNumber(ch
) {
5865 return '0' <= ch
&& ch
<= '9';
5867 function isWhitespace(ch
) {
5868 return ch
== ' ' || ch
== '\r' || ch
== '\t' ||
5869 ch
== '\n' || ch
== '\v' || ch
== '\u00A0'; // IE treats non-breaking space as \u00A0
5871 function isIdent(ch
) {
5872 return 'a' <= ch
&& ch
<= 'z' ||
5873 'A' <= ch
&& ch
<= 'Z' ||
5874 '_' == ch
|| ch
== '$';
5876 function isExpOperator(ch
) {
5877 return ch
== '-' || ch
== '+' || isNumber(ch
);
5880 function throwError(error
, start
, end
) {
5882 throw Error("Lexer Error: " + error
+ " at column" +
5884 ? "s " + start
+ "-" + index
+ " [" + text
.substring(start
, end
) + "]"
5886 " in expression [" + text
+ "].");
5889 function readNumber() {
5892 while (index
< text
.length
) {
5893 var ch
= lowercase(text
.charAt(index
));
5894 if (ch
== '.' || isNumber(ch
)) {
5897 var peekCh
= peek();
5898 if (ch
== 'e' && isExpOperator(peekCh
)) {
5900 } else if (isExpOperator(ch
) &&
5901 peekCh
&& isNumber(peekCh
) &&
5902 number
.charAt(number
.length
- 1) == 'e') {
5904 } else if (isExpOperator(ch
) &&
5905 (!peekCh
|| !isNumber(peekCh
)) &&
5906 number
.charAt(number
.length
- 1) == 'e') {
5907 throwError('Invalid exponent');
5914 number
= 1 * number
;
5915 tokens
.push({index:start
, text:number
, json:true,
5916 fn:function() {return number
;}});
5918 function readIdent() {
5921 lastDot
, peekIndex
, methodName
, ch
;
5923 while (index
< text
.length
) {
5924 ch
= text
.charAt(index
);
5925 if (ch
== '.' || isIdent(ch
) || isNumber(ch
)) {
5926 if (ch
== '.') lastDot
= index
;
5934 //check if this is not a method invocation and if it is back out to last dot
5937 while(peekIndex
< text
.length
) {
5938 ch
= text
.charAt(peekIndex
);
5940 methodName
= ident
.substr(lastDot
- start
+ 1);
5941 ident
= ident
.substr(0, lastDot
- start
);
5945 if(isWhitespace(ch
)) {
5959 if (OPERATORS
.hasOwnProperty(ident
)) {
5960 token
.fn
= token
.json
= OPERATORS
[ident
];
5962 var getter
= getterFn(ident
, csp
);
5963 token
.fn
= extend(function(self
, locals
) {
5964 return (getter(self
, locals
));
5966 assign: function(self
, value
) {
5967 return setter(self
, ident
, value
);
5988 function readString(quote
) {
5992 var rawString
= quote
;
5994 while (index
< text
.length
) {
5995 var ch
= text
.charAt(index
);
5999 var hex
= text
.substring(index
+ 1, index
+ 5);
6000 if (!hex
.match(/[\da-f]{4}/i))
6001 throwError( "Invalid unicode escape [\\u" + hex
+ "]");
6003 string
+= String
.fromCharCode(parseInt(hex
, 16));
6005 var rep
= ESCAPE
[ch
];
6013 } else if (ch
== '\\') {
6015 } else if (ch
== quote
) {
6022 fn:function() { return string
; }
6030 throwError("Unterminated quote", start
);
6034 /////////////////////////////////////////
6036 function parser(text
, json
, $filter
, csp
){
6037 var ZERO
= valueFn(0),
6039 tokens
= lex(text
, csp
),
6040 assignment
= _assignment
,
6041 functionCall
= _functionCall
,
6042 fieldAccess
= _fieldAccess
,
6043 objectIndex
= _objectIndex
,
6044 filterChain
= _filterChain
;
6047 // The extra level of aliasing is here, just in case the lexer misses something, so that
6048 // we prevent any accidental execution in JSON.
6049 assignment
= logicalOR
;
6054 function() { throwError("is not valid json", {text:text
, index:0}); };
6057 value
= statements();
6059 if (tokens
.length
!== 0) {
6060 throwError("is an unexpected token", tokens
[0]);
6064 ///////////////////////////////////
6065 function throwError(msg
, token
) {
6066 throw Error("Syntax Error: Token '" + token
.text
+
6067 "' " + msg
+ " at column " +
6068 (token
.index
+ 1) + " of the expression [" +
6069 text
+ "] starting at [" + text
.substring(token
.index
) + "].");
6072 function peekToken() {
6073 if (tokens
.length
=== 0)
6074 throw Error("Unexpected end of expression: " + text
);
6078 function peek(e1
, e2
, e3
, e4
) {
6079 if (tokens
.length
> 0) {
6080 var token
= tokens
[0];
6082 if (t
==e1
|| t
==e2
|| t
==e3
|| t
==e4
||
6083 (!e1
&& !e2
&& !e3
&& !e4
)) {
6090 function expect(e1
, e2
, e3
, e4
){
6091 var token
= peek(e1
, e2
, e3
, e4
);
6093 if (json
&& !token
.json
) {
6094 throwError("is not valid json", token
);
6102 function consume(e1
){
6104 throwError("is unexpected, expecting [" + e1
+ "]", peek());
6108 function unaryFn(fn
, right
) {
6109 return function(self
, locals
) {
6110 return fn(self
, locals
, right
);
6114 function binaryFn(left
, fn
, right
) {
6115 return function(self
, locals
) {
6116 return fn(self
, locals
, left
, right
);
6120 function statements() {
6121 var statements
= [];
6123 if (tokens
.length
> 0 && !peek('}', ')', ';', ']'))
6124 statements
.push(filterChain());
6126 // optimize for the common case where there is only one statement.
6127 // TODO(size): maybe we should not support multiple statements?
6128 return statements
.length
== 1
6130 : function(self
, locals
){
6132 for ( var i
= 0; i
< statements
.length
; i
++) {
6133 var statement
= statements
[i
];
6135 value
= statement(self
, locals
);
6143 function _filterChain() {
6144 var left
= expression();
6147 if ((token
= expect('|'))) {
6148 left
= binaryFn(left
, token
.fn
, filter());
6156 var token
= expect();
6157 var fn
= $filter(token
.text
);
6160 if ((token
= expect(':'))) {
6161 argsFn
.push(expression());
6163 var fnInvoke = function(self
, locals
, input
){
6165 for ( var i
= 0; i
< argsFn
.length
; i
++) {
6166 args
.push(argsFn
[i
](self
, locals
));
6168 return fn
.apply(self
, args
);
6177 function expression() {
6178 return assignment();
6181 function _assignment() {
6182 var left
= logicalOR();
6185 if ((token
= expect('='))) {
6187 throwError("implies assignment but [" +
6188 text
.substring(0, token
.index
) + "] can not be assigned to", token
);
6190 right
= logicalOR();
6191 return function(scope
, locals
){
6192 return left
.assign(scope
, right(scope
, locals
), locals
);
6199 function logicalOR() {
6200 var left
= logicalAND();
6203 if ((token
= expect('||'))) {
6204 left
= binaryFn(left
, token
.fn
, logicalAND());
6211 function logicalAND() {
6212 var left
= equality();
6214 if ((token
= expect('&&'))) {
6215 left
= binaryFn(left
, token
.fn
, logicalAND());
6220 function equality() {
6221 var left
= relational();
6223 if ((token
= expect('==','!='))) {
6224 left
= binaryFn(left
, token
.fn
, equality());
6229 function relational() {
6230 var left
= additive();
6232 if ((token
= expect('<', '>', '<=', '>='))) {
6233 left
= binaryFn(left
, token
.fn
, relational());
6238 function additive() {
6239 var left
= multiplicative();
6241 while ((token
= expect('+','-'))) {
6242 left
= binaryFn(left
, token
.fn
, multiplicative());
6247 function multiplicative() {
6250 while ((token
= expect('*','/','%'))) {
6251 left
= binaryFn(left
, token
.fn
, unary());
6260 } else if ((token
= expect('-'))) {
6261 return binaryFn(ZERO
, token
.fn
, unary());
6262 } else if ((token
= expect('!'))) {
6263 return unaryFn(token
.fn
, unary());
6270 function primary() {
6273 primary
= filterChain();
6275 } else if (expect('[')) {
6276 primary
= arrayDeclaration();
6277 } else if (expect('{')) {
6280 var token
= expect();
6283 throwError("not a primary expression", token
);
6288 while ((next
= expect('(', '[', '.'))) {
6289 if (next
.text
=== '(') {
6290 primary
= functionCall(primary
, context
);
6292 } else if (next
.text
=== '[') {
6294 primary
= objectIndex(primary
);
6295 } else if (next
.text
=== '.') {
6297 primary
= fieldAccess(primary
);
6299 throwError("IMPOSSIBLE");
6305 function _fieldAccess(object
) {
6306 var field
= expect().text
;
6307 var getter
= getterFn(field
, csp
);
6309 function(scope
, locals
, self
) {
6310 return getter(self
|| object(scope
, locals
), locals
);
6313 assign:function(scope
, value
, locals
) {
6314 return setter(object(scope
, locals
), field
, value
);
6320 function _objectIndex(obj
) {
6321 var indexFn
= expression();
6324 function(self
, locals
){
6325 var o
= obj(self
, locals
),
6326 i
= indexFn(self
, locals
),
6329 if (!o
) return undefined;
6333 if (!('$$v' in v
)) {
6335 p
.then(function(val
) { p
.$$v
= val
; });
6341 assign:function(self
, value
, locals
){
6342 return obj(self
, locals
)[indexFn(self
, locals
)] = value
;
6347 function _functionCall(fn
, contextGetter
) {
6349 if (peekToken().text
!= ')') {
6351 argsFn
.push(expression());
6352 } while (expect(','));
6355 return function(scope
, locals
){
6357 context
= contextGetter
? contextGetter(scope
, locals
) : scope
;
6359 for ( var i
= 0; i
< argsFn
.length
; i
++) {
6360 args
.push(argsFn
[i
](scope
, locals
));
6362 var fnPtr
= fn(scope
, locals
, context
) || noop
;
6365 ? fnPtr
.apply(context
, args
)
6366 : fnPtr(args
[0], args
[1], args
[2], args
[3], args
[4]);
6370 // This is used with json array declaration
6371 function arrayDeclaration () {
6372 var elementFns
= [];
6373 if (peekToken().text
!= ']') {
6375 elementFns
.push(expression());
6376 } while (expect(','));
6379 return function(self
, locals
){
6381 for ( var i
= 0; i
< elementFns
.length
; i
++) {
6382 array
.push(elementFns
[i
](self
, locals
));
6388 function object () {
6390 if (peekToken().text
!= '}') {
6392 var token
= expect(),
6393 key
= token
.string
|| token
.text
;
6395 var value
= expression();
6396 keyValues
.push({key:key
, value:value
});
6397 } while (expect(','));
6400 return function(self
, locals
){
6402 for ( var i
= 0; i
< keyValues
.length
; i
++) {
6403 var keyValue
= keyValues
[i
];
6404 object
[keyValue
.key
] = keyValue
.value(self
, locals
);
6411 //////////////////////////////////////////////////
6412 // Parser helper functions
6413 //////////////////////////////////////////////////
6415 function setter(obj
, path
, setValue
) {
6416 var element
= path
.split('.');
6417 for (var i
= 0; element
.length
> 1; i
++) {
6418 var key
= element
.shift();
6419 var propertyObj
= obj
[key
];
6422 obj
[key
] = propertyObj
;
6426 obj
[element
.shift()] = setValue
;
6431 * Return the value accesible from the object by path. Any undefined traversals are ignored
6432 * @param {Object} obj starting object
6433 * @param {string} path path to traverse
6434 * @param {boolean=true} bindFnToScope
6435 * @returns value as accesbile by path
6437 //TODO(misko): this function needs to be removed
6438 function getter(obj
, path
, bindFnToScope
) {
6439 if (!path
) return obj
;
6440 var keys
= path
.split('.');
6442 var lastInstance
= obj
;
6443 var len
= keys
.length
;
6445 for (var i
= 0; i
< len
; i
++) {
6448 obj
= (lastInstance
= obj
)[key
];
6451 if (!bindFnToScope
&& isFunction(obj
)) {
6452 return bind(lastInstance
, obj
);
6457 var getterFnCache
= {};
6460 * Implementation of the "Black Hole" variant from:
6461 * - http://jsperf.com/angularjs-parse-getter/4
6462 * - http://jsperf.com/path-evaluation-simplified/7
6464 function cspSafeGetterFn(key0
, key1
, key2
, key3
, key4
) {
6465 return function(scope
, locals
) {
6466 var pathVal
= (locals
&& locals
.hasOwnProperty(key0
)) ? locals : scope
,
6469 if (pathVal
=== null || pathVal
=== undefined) return pathVal
;
6471 pathVal
= pathVal
[key0
];
6472 if (pathVal
&& pathVal
.then
) {
6473 if (!("$$v" in pathVal
)) {
6475 promise
.$$v
= undefined;
6476 promise
.then(function(val
) { promise
.$$v
= val
; });
6478 pathVal
= pathVal
.$$v
;
6480 if (!key1
|| pathVal
=== null || pathVal
=== undefined) return pathVal
;
6482 pathVal
= pathVal
[key1
];
6483 if (pathVal
&& pathVal
.then
) {
6484 if (!("$$v" in pathVal
)) {
6486 promise
.$$v
= undefined;
6487 promise
.then(function(val
) { promise
.$$v
= val
; });
6489 pathVal
= pathVal
.$$v
;
6491 if (!key2
|| pathVal
=== null || pathVal
=== undefined) return pathVal
;
6493 pathVal
= pathVal
[key2
];
6494 if (pathVal
&& pathVal
.then
) {
6495 if (!("$$v" in pathVal
)) {
6497 promise
.$$v
= undefined;
6498 promise
.then(function(val
) { promise
.$$v
= val
; });
6500 pathVal
= pathVal
.$$v
;
6502 if (!key3
|| pathVal
=== null || pathVal
=== undefined) return pathVal
;
6504 pathVal
= pathVal
[key3
];
6505 if (pathVal
&& pathVal
.then
) {
6506 if (!("$$v" in pathVal
)) {
6508 promise
.$$v
= undefined;
6509 promise
.then(function(val
) { promise
.$$v
= val
; });
6511 pathVal
= pathVal
.$$v
;
6513 if (!key4
|| pathVal
=== null || pathVal
=== undefined) return pathVal
;
6515 pathVal
= pathVal
[key4
];
6516 if (pathVal
&& pathVal
.then
) {
6517 if (!("$$v" in pathVal
)) {
6519 promise
.$$v
= undefined;
6520 promise
.then(function(val
) { promise
.$$v
= val
; });
6522 pathVal
= pathVal
.$$v
;
6528 function getterFn(path
, csp
) {
6529 if (getterFnCache
.hasOwnProperty(path
)) {
6530 return getterFnCache
[path
];
6533 var pathKeys
= path
.split('.'),
6534 pathKeysLength
= pathKeys
.length
,
6538 fn
= (pathKeysLength
< 6)
6539 ? cspSafeGetterFn(pathKeys
[0], pathKeys
[1], pathKeys
[2], pathKeys
[3], pathKeys
[4])
6540 : function(scope
, locals
) {
6543 val
= cspSafeGetterFn(
6544 pathKeys
[i
++], pathKeys
[i
++], pathKeys
[i
++], pathKeys
[i
++], pathKeys
[i
++]
6547 locals
= undefined; // clear after first iteration
6549 } while (i
< pathKeysLength
);
6553 var code
= 'var l, fn, p;\n';
6554 forEach(pathKeys
, function(key
, index
) {
6555 code
+= 'if(s === null || s === undefined) return s;\n' +
6558 // we simply dereference 's' on any .dot notation
6560 // but if we are first then we check locals first, and if so read it first
6561 : '((k&&k.hasOwnProperty("' + key
+ '"))?k:s)') + '["' + key
+ '"]' + ';\n' +
6562 'if (s && s.then) {\n' +
6563 ' if (!("$$v" in s)) {\n' +
6565 ' p.$$v = undefined;\n' +
6566 ' p.then(function(v) {p.$$v=v;});\n' +
6571 code
+= 'return s;';
6572 fn
= Function('s', 'k', code
); // s=scope, k=locals
6573 fn
.toString = function() { return code
; };
6576 return getterFnCache
[path
] = fn
;
6579 ///////////////////////////////////
6588 * Converts Angular {@link guide/expression expression} into a function.
6591 * var getter = $parse('user.name');
6592 * var setter = getter.assign;
6593 * var context = {user:{name:'angular'}};
6594 * var locals = {user:{name:'local'}};
6596 * expect(getter(context)).toEqual('angular');
6597 * setter(context, 'newValue');
6598 * expect(context.user.name).toEqual('newValue');
6599 * expect(getter(context, locals)).toEqual('local');
6603 * @param {string} expression String expression to compile.
6604 * @returns {function(context, locals)} a function which represents the compiled expression:
6606 * * `context` – `{object}` – an object against which any expressions embedded in the strings
6607 * are evaluated against (tipically a scope object).
6608 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
6611 * The return function also has an `assign` property, if the expression is assignable, which
6612 * allows one to set values to expressions.
6615 function $ParseProvider() {
6617 this.$get = ['$filter', '$sniffer', function($filter
, $sniffer
) {
6618 return function(exp
) {
6619 switch(typeof exp
) {
6621 return cache
.hasOwnProperty(exp
)
6623 : cache
[exp
] = parser(exp
, false, $filter
, $sniffer
.csp
);
6636 * @requires $rootScope
6639 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
6641 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
6642 * interface for interacting with an object that represents the result of an action that is
6643 * performed asynchronously, and may or may not be finished at any given point in time.
6645 * From the perspective of dealing with error handling, deferred and promise APIs are to
6646 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
6649 * // for the purpose of this example let's assume that variables `$q` and `scope` are
6650 * // available in the current lexical scope (they could have been injected or passed in).
6652 * function asyncGreet(name) {
6653 * var deferred = $q.defer();
6655 * setTimeout(function() {
6656 * // since this fn executes async in a future turn of the event loop, we need to wrap
6657 * // our code into an $apply call so that the model changes are properly observed.
6658 * scope.$apply(function() {
6659 * if (okToGreet(name)) {
6660 * deferred.resolve('Hello, ' + name + '!');
6662 * deferred.reject('Greeting ' + name + ' is not allowed.');
6667 * return deferred.promise;
6670 * var promise = asyncGreet('Robin Hood');
6671 * promise.then(function(greeting) {
6672 * alert('Success: ' + greeting);
6673 * }, function(reason) {
6674 * alert('Failed: ' + reason);
6678 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
6679 * comes in the way of
6680 * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
6682 * Additionally the promise api allows for composition that is very hard to do with the
6683 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
6684 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
6685 * section on serial or parallel joining of promises.
6688 * # The Deferred API
6690 * A new instance of deferred is constructed by calling `$q.defer()`.
6692 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
6693 * that can be used for signaling the successful or unsuccessful completion of the task.
6697 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
6698 * constructed via `$q.reject`, the promise will be rejected instead.
6699 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
6700 * resolving it with a rejection constructed via `$q.reject`.
6704 * - promise – `{Promise}` – promise object associated with this deferred.
6709 * A new promise instance is created when a deferred instance is created and can be retrieved by
6710 * calling `deferred.promise`.
6712 * The purpose of the promise object is to allow for interested parties to get access to the result
6713 * of the deferred task when it completes.
6717 * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
6718 * or rejected calls one of the success or error callbacks asynchronously as soon as the result
6719 * is available. The callbacks are called with a single argument the result or rejection reason.
6721 * This method *returns a new promise* which is resolved or rejected via the return value of the
6722 * `successCallback` or `errorCallback`.
6725 * # Chaining promises
6727 * Because calling `then` api of a promise returns a new derived promise, it is easily possible
6728 * to create a chain of promises:
6731 * promiseB = promiseA.then(function(result) {
6732 * return result + 1;
6735 * // promiseB will be resolved immediately after promiseA is resolved and its value will be
6736 * // the result of promiseA incremented by 1
6739 * It is possible to create chains of any length and since a promise can be resolved with another
6740 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
6741 * the promises at any point in the chain. This makes it possible to implement powerful apis like
6742 * $http's response interceptors.
6745 * # Differences between Kris Kowal's Q and $q
6747 * There are three main differences:
6749 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
6750 * mechanism in angular, which means faster propagation of resolution or rejection into your
6751 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
6752 * - $q promises are recognized by the templating engine in angular, which means that in templates
6753 * you can treat promises attached to a scope as if they were the resulting values.
6754 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
6755 * all the important functionality needed for common async tasks.
6760 * it('should simulate promise', inject(function($q, $rootScope) {
6761 * var deferred = $q.defer();
6762 * var promise = deferred.promise;
6763 * var resolvedValue;
6765 * promise.then(function(value) { resolvedValue = value; });
6766 * expect(resolvedValue).toBeUndefined();
6768 * // Simulate resolving of promise
6769 * deferred.resolve(123);
6770 * // Note that the 'then' function does not get called synchronously.
6771 * // This is because we want the promise API to always be async, whether or not
6772 * // it got called synchronously or asynchronously.
6773 * expect(resolvedValue).toBeUndefined();
6775 * // Propagate promise resolution to 'then' functions using $apply().
6776 * $rootScope.$apply();
6777 * expect(resolvedValue).toEqual(123);
6781 function $QProvider() {
6783 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope
, $exceptionHandler
) {
6784 return qFactory(function(callback
) {
6785 $rootScope
.$evalAsync(callback
);
6786 }, $exceptionHandler
);
6792 * Constructs a promise manager.
6794 * @param {function(function)} nextTick Function for executing functions in the next turn.
6795 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
6796 * debugging purposes.
6797 * @returns {object} Promise manager.
6799 function qFactory(nextTick
, exceptionHandler
) {
6806 * Creates a `Deferred` object which represents a task which will finish in the future.
6808 * @returns {Deferred} Returns a new instance of deferred.
6810 var defer = function() {
6816 resolve: function(val
) {
6818 var callbacks
= pending
;
6819 pending
= undefined;
6822 if (callbacks
.length
) {
6823 nextTick(function() {
6825 for (var i
= 0, ii
= callbacks
.length
; i
< ii
; i
++) {
6826 callback
= callbacks
[i
];
6827 value
.then(callback
[0], callback
[1]);
6835 reject: function(reason
) {
6836 deferred
.resolve(reject(reason
));
6841 then: function(callback
, errback
) {
6842 var result
= defer();
6844 var wrappedCallback = function(value
) {
6846 result
.resolve((callback
|| defaultCallback
)(value
));
6848 exceptionHandler(e
);
6853 var wrappedErrback = function(reason
) {
6855 result
.resolve((errback
|| defaultErrback
)(reason
));
6857 exceptionHandler(e
);
6863 pending
.push([wrappedCallback
, wrappedErrback
]);
6865 value
.then(wrappedCallback
, wrappedErrback
);
6868 return result
.promise
;
6877 var ref = function(value
) {
6878 if (value
&& value
.then
) return value
;
6880 then: function(callback
) {
6881 var result
= defer();
6882 nextTick(function() {
6883 result
.resolve(callback(value
));
6885 return result
.promise
;
6893 * @name ng.$q#reject
6896 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
6897 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
6898 * a promise chain, you don't need to worry about it.
6900 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
6901 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
6902 * a promise error callback and you want to forward the error to the promise derived from the
6903 * current promise, you have to "rethrow" the error by returning a rejection constructed via
6907 * promiseB = promiseA.then(function(result) {
6908 * // success: do something and resolve promiseB
6909 * // with the old or a new result
6911 * }, function(reason) {
6912 * // error: handle the error if possible and
6913 * // resolve promiseB with newPromiseOrValue,
6914 * // otherwise forward the rejection to promiseB
6915 * if (canHandle(reason)) {
6916 * // handle the error and recover
6917 * return newPromiseOrValue;
6919 * return $q.reject(reason);
6923 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
6924 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
6926 var reject = function(reason
) {
6928 then: function(callback
, errback
) {
6929 var result
= defer();
6930 nextTick(function() {
6931 result
.resolve((errback
|| defaultErrback
)(reason
));
6933 return result
.promise
;
6944 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
6945 * This is useful when you are dealing with an object that might or might not be a promise, or if
6946 * the promise comes from a source that can't be trusted.
6948 * @param {*} value Value or a promise
6949 * @returns {Promise} Returns a promise of the passed value or promise
6951 var when = function(value
, callback
, errback
) {
6952 var result
= defer(),
6955 var wrappedCallback = function(value
) {
6957 return (callback
|| defaultCallback
)(value
);
6959 exceptionHandler(e
);
6964 var wrappedErrback = function(reason
) {
6966 return (errback
|| defaultErrback
)(reason
);
6968 exceptionHandler(e
);
6973 nextTick(function() {
6974 ref(value
).then(function(value
) {
6977 result
.resolve(ref(value
).then(wrappedCallback
, wrappedErrback
));
6978 }, function(reason
) {
6981 result
.resolve(wrappedErrback(reason
));
6985 return result
.promise
;
6989 function defaultCallback(value
) {
6994 function defaultErrback(reason
) {
6995 return reject(reason
);
7004 * Combines multiple promises into a single promise that is resolved when all of the input
7005 * promises are resolved.
7007 * @param {Array.<Promise>} promises An array of promises.
7008 * @returns {Promise} Returns a single promise that will be resolved with an array of values,
7009 * each value corresponding to the promise at the same index in the `promises` array. If any of
7010 * the promises is resolved with a rejection, this resulting promise will be resolved with the
7013 function all(promises
) {
7014 var deferred
= defer(),
7015 counter
= promises
.length
,
7019 forEach(promises
, function(promise
, index
) {
7020 ref(promise
).then(function(value
) {
7021 if (index
in results
) return;
7022 results
[index
] = value
;
7023 if (!(--counter
)) deferred
.resolve(results
);
7024 }, function(reason
) {
7025 if (index
in results
) return;
7026 deferred
.reject(reason
);
7030 deferred
.resolve(results
);
7033 return deferred
.promise
;
7046 * @name ng.$routeProvider
7051 * Used for configuring routes. See {@link ng.$route $route} for an example.
7053 function $RouteProvider(){
7058 * @name ng.$routeProvider#when
7059 * @methodOf ng.$routeProvider
7061 * @param {string} path Route path (matched against `$location.path`). If `$location.path`
7062 * contains redundant trailing slash or is missing one, the route will still match and the
7063 * `$location.path` will be updated to add or drop the trailing slash to exactly match the
7066 * `path` can contain named groups starting with a colon (`:name`). All characters up to the
7067 * next slash are matched and stored in `$routeParams` under the given `name` when the route
7070 * @param {Object} route Mapping information to be assigned to `$route.current` on route
7073 * Object properties:
7075 * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly
7076 * created scope or the name of a {@link angular.Module#controller registered controller}
7077 * if passed as a string.
7078 * - `template` – `{string=}` – html template as a string that should be used by
7079 * {@link ng.directive:ngView ngView} or
7080 * {@link ng.directive:ngInclude ngInclude} directives.
7081 * this property takes precedence over `templateUrl`.
7082 * - `templateUrl` – `{string=}` – path to an html template that should be used by
7083 * {@link ng.directive:ngView ngView}.
7084 * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
7085 * be injected into the controller. If any of these dependencies are promises, they will be
7086 * resolved and converted to a value before the controller is instantiated and the
7087 * `$routeChangeSuccess` event is fired. The map object is:
7089 * - `key` – `{string}`: a name of a dependency to be injected into the controller.
7090 * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
7091 * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
7092 * and the return value is treated as the dependency. If the result is a promise, it is resolved
7093 * before its value is injected into the controller.
7095 * - `redirectTo` – {(string|function())=} – value to update
7096 * {@link ng.$location $location} path with and trigger route redirection.
7098 * If `redirectTo` is a function, it will be called with the following parameters:
7100 * - `{Object.<string>}` - route parameters extracted from the current
7101 * `$location.path()` by applying the current route templateUrl.
7102 * - `{string}` - current `$location.path()`
7103 * - `{Object}` - current `$location.search()`
7105 * The custom `redirectTo` function is expected to return a string which will be used
7106 * to update `$location.path()` and `$location.search()`.
7108 * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
7111 * If the option is set to `false` and url in the browser changes, then
7112 * `$routeUpdate` event is broadcasted on the root scope.
7114 * @returns {Object} self
7117 * Adds a new route definition to the `$route` service.
7119 this.when = function(path
, route
) {
7120 routes
[path
] = extend({reloadOnSearch: true}, route
);
7122 // create redirection for trailing slashes
7124 var redirectPath
= (path
[path
.length
-1] == '/')
7125 ? path
.substr(0, path
.length
-1)
7128 routes
[redirectPath
] = {redirectTo: path
};
7136 * @name ng.$routeProvider#otherwise
7137 * @methodOf ng.$routeProvider
7140 * Sets route definition that will be used on route change when no other route definition
7143 * @param {Object} params Mapping information to be assigned to `$route.current`.
7144 * @returns {Object} self
7146 this.otherwise = function(params
) {
7147 this.when(null, params
);
7152 this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
7153 function( $rootScope
, $location
, $routeParams
, $q
, $injector
, $http
, $templateCache
) {
7158 * @requires $location
7159 * @requires $routeParams
7161 * @property {Object} current Reference to the current route definition.
7162 * The route definition contains:
7164 * - `controller`: The controller constructor as define in route definition.
7165 * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
7166 * controller instantiation. The `locals` contain
7167 * the resolved values of the `resolve` map. Additionally the `locals` also contain:
7169 * - `$scope` - The current route scope.
7170 * - `$template` - The current route template HTML.
7172 * @property {Array.<Object>} routes Array of all configured routes.
7175 * Is used for deep-linking URLs to controllers and views (HTML partials).
7176 * It watches `$location.url()` and tries to map the path to an existing route definition.
7178 * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
7180 * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
7181 * directive and the {@link ng.$routeParams $routeParams} service.
7184 This example shows how changing the URL hash causes the `$route` to match a route against the
7185 URL, and the `ngView` pulls in the partial.
7187 Note that this example is using {@link ng.directive:script inlined templates}
7188 to get it working on jsfiddle as well.
7190 <example module="ngView">
7191 <file name="index.html">
7192 <div ng-controller="MainCntl">
7194 <a href="Book/Moby">Moby</a> |
7195 <a href="Book/Moby/ch/1">Moby: Ch1</a> |
7196 <a href="Book/Gatsby">Gatsby</a> |
7197 <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
7198 <a href="Book/Scarlet">Scarlet Letter</a><br/>
7203 <pre>$location.path() = {{$location.path()}}</pre>
7204 <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
7205 <pre>$route.current.params = {{$route.current.params}}</pre>
7206 <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
7207 <pre>$routeParams = {{$routeParams}}</pre>
7211 <file name="book.html">
7212 controller: {{name}}<br />
7213 Book Id: {{params.bookId}}<br />
7216 <file name="chapter.html">
7217 controller: {{name}}<br />
7218 Book Id: {{params.bookId}}<br />
7219 Chapter Id: {{params.chapterId}}
7222 <file name="script.js">
7223 angular.module('ngView', [], function($routeProvider, $locationProvider) {
7224 $routeProvider.when('/Book/:bookId', {
7225 templateUrl: 'book.html',
7226 controller: BookCntl,
7228 // I will cause a 1 second delay
7229 delay: function($q, $timeout) {
7230 var delay = $q.defer();
7231 $timeout(delay.resolve, 1000);
7232 return delay.promise;
7236 $routeProvider.when('/Book/:bookId/ch/:chapterId', {
7237 templateUrl: 'chapter.html',
7238 controller: ChapterCntl
7241 // configure html5 to get links working on jsfiddle
7242 $locationProvider.html5Mode(true);
7245 function MainCntl($scope, $route, $routeParams, $location) {
7246 $scope.$route = $route;
7247 $scope.$location = $location;
7248 $scope.$routeParams = $routeParams;
7251 function BookCntl($scope, $routeParams) {
7252 $scope.name = "BookCntl";
7253 $scope.params = $routeParams;
7256 function ChapterCntl($scope, $routeParams) {
7257 $scope.name = "ChapterCntl";
7258 $scope.params = $routeParams;
7262 <file name="scenario.js">
7263 it('should load and compile correct template', function() {
7264 element('a:contains("Moby: Ch1")').click();
7265 var content = element('.doc-example-live [ng-view]').text();
7266 expect(content).toMatch(/controller\: ChapterCntl/);
7267 expect(content).toMatch(/Book Id\: Moby/);
7268 expect(content).toMatch(/Chapter Id\: 1/);
7270 element('a:contains("Scarlet")').click();
7271 sleep(2); // promises are not part of scenario waiting
7272 content = element('.doc-example-live [ng-view]').text();
7273 expect(content).toMatch(/controller\: BookCntl/);
7274 expect(content).toMatch(/Book Id\: Scarlet/);
7282 * @name ng.$route#$routeChangeStart
7283 * @eventOf ng.$route
7284 * @eventType broadcast on root scope
7286 * Broadcasted before a route change. At this point the route services starts
7287 * resolving all of the dependencies needed for the route change to occurs.
7288 * Typically this involves fetching the view template as well as any dependencies
7289 * defined in `resolve` route property. Once all of the dependencies are resolved
7290 * `$routeChangeSuccess` is fired.
7292 * @param {Route} next Future route information.
7293 * @param {Route} current Current route information.
7298 * @name ng.$route#$routeChangeSuccess
7299 * @eventOf ng.$route
7300 * @eventType broadcast on root scope
7302 * Broadcasted after a route dependencies are resolved.
7303 * {@link ng.directive:ngView ngView} listens for the directive
7304 * to instantiate the controller and render the view.
7306 * @param {Object} angularEvent Synthetic event object.
7307 * @param {Route} current Current route information.
7308 * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered.
7313 * @name ng.$route#$routeChangeError
7314 * @eventOf ng.$route
7315 * @eventType broadcast on root scope
7317 * Broadcasted if any of the resolve promises are rejected.
7319 * @param {Route} current Current route information.
7320 * @param {Route} previous Previous route information.
7321 * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
7326 * @name ng.$route#$routeUpdate
7327 * @eventOf ng.$route
7328 * @eventType broadcast on root scope
7331 * The `reloadOnSearch` property has been set to false, and we are reusing the same
7332 * instance of the Controller.
7335 var forceReload
= false,
7341 * @name ng.$route#reload
7342 * @methodOf ng.$route
7345 * Causes `$route` service to reload the current route even if
7346 * {@link ng.$location $location} hasn't changed.
7348 * As a result of that, {@link ng.directive:ngView ngView}
7349 * creates new scope, reinstantiates the controller.
7351 reload: function() {
7353 $rootScope
.$evalAsync(updateRoute
);
7357 $rootScope
.$on('$locationChangeSuccess', updateRoute
);
7361 /////////////////////////////////////////////////////
7364 * @param on {string} current url
7365 * @param when {string} route when template to match the url against
7368 function switchRouteMatcher(on
, when
) {
7369 // TODO(i): this code is convoluted and inefficient, we should construct the route matching
7370 // regex only once and then reuse it
7372 // Escape regexp special characters.
7373 when
= '^' + when
.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
7380 lastMatchedIndex
= 0;
7382 while ((paramMatch
= re
.exec(when
)) !== null) {
7383 // Find each :param in `when` and replace it with a capturing group.
7384 // Append all other sections of when unchanged.
7385 regex
+= when
.slice(lastMatchedIndex
, paramMatch
.index
);
7386 regex
+= '([^\\/]*)';
7387 params
.push(paramMatch
[1]);
7388 lastMatchedIndex
= re
.lastIndex
;
7390 // Append trailing path part.
7391 regex
+= when
.substr(lastMatchedIndex
);
7393 var match
= on
.match(new RegExp(regex
));
7395 forEach(params
, function(name
, index
) {
7396 dst
[name
] = match
[index
+ 1];
7399 return match
? dst : null;
7402 function updateRoute() {
7403 var next
= parseRoute(),
7404 last
= $route
.current
;
7406 if (next
&& last
&& next
.$$route
=== last
.$$route
7407 && equals(next
.pathParams
, last
.pathParams
) && !next
.reloadOnSearch
&& !forceReload
) {
7408 last
.params
= next
.params
;
7409 copy(last
.params
, $routeParams
);
7410 $rootScope
.$broadcast('$routeUpdate', last
);
7411 } else if (next
|| last
) {
7412 forceReload
= false;
7413 $rootScope
.$broadcast('$routeChangeStart', next
, last
);
7414 $route
.current
= next
;
7416 if (next
.redirectTo
) {
7417 if (isString(next
.redirectTo
)) {
7418 $location
.path(interpolate(next
.redirectTo
, next
.params
)).search(next
.params
)
7421 $location
.url(next
.redirectTo(next
.pathParams
, $location
.path(), $location
.search()))
7434 forEach(next
.resolve
|| {}, function(value
, key
) {
7436 values
.push(isString(value
) ? $injector
.get(value
) : $injector
.invoke(value
));
7438 if (isDefined(template
= next
.template
)) {
7439 } else if (isDefined(template
= next
.templateUrl
)) {
7440 template
= $http
.get(template
, {cache: $templateCache
}).
7441 then(function(response
) { return response
.data
; });
7443 if (isDefined(template
)) {
7444 keys
.push('$template');
7445 values
.push(template
);
7447 return $q
.all(values
).then(function(values
) {
7449 forEach(values
, function(value
, index
) {
7450 locals
[keys
[index
]] = value
;
7456 // after route change
7457 then(function(locals
) {
7458 if (next
== $route
.current
) {
7460 next
.locals
= locals
;
7461 copy(next
.params
, $routeParams
);
7463 $rootScope
.$broadcast('$routeChangeSuccess', next
, last
);
7465 }, function(error
) {
7466 if (next
== $route
.current
) {
7467 $rootScope
.$broadcast('$routeChangeError', next
, last
, error
);
7475 * @returns the current active route, by matching it against the URL
7477 function parseRoute() {
7480 forEach(routes
, function(route
, path
) {
7481 if (!match
&& (params
= switchRouteMatcher($location
.path(), path
))) {
7482 match
= inherit(route
, {
7483 params: extend({}, $location
.search(), params
),
7484 pathParams: params
});
7485 match
.$$route
= route
;
7488 // No route matched; fallback to "otherwise" route
7489 return match
|| routes
[null] && inherit(routes
[null], {params: {}, pathParams:{}});
7493 * @returns interpolation of the redirect path with the parametrs
7495 function interpolate(string
, params
) {
7497 forEach((string
||'').split(':'), function(segment
, i
) {
7499 result
.push(segment
);
7501 var segmentMatch
= segment
.match(/(\w+)(.*)/);
7502 var key
= segmentMatch
[1];
7503 result
.push(params
[key
]);
7504 result
.push(segmentMatch
[2] || '');
7508 return result
.join('');
7515 * @name ng.$routeParams
7519 * Current set of route parameters. The route parameters are a combination of the
7520 * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters
7521 * are extracted when the {@link ng.$route $route} path is matched.
7523 * In case of parameter name collision, `path` params take precedence over `search` params.
7525 * The service guarantees that the identity of the `$routeParams` object will remain unchanged
7526 * (but its properties will likely change) even when a route change occurs.
7531 * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
7532 * // Route: /Chapter/:chapterId/Section/:sectionId
7535 * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
7538 function $RouteParamsProvider() {
7539 this.$get = valueFn({});
7545 * The design decisions behind the scope are heavily favored for speed and memory consumption.
7547 * The typical use of scope is to watch the expressions, which most of the time return the same
7548 * value as last time so we optimize the operation.
7550 * Closures construction is expensive in terms of speed as well as memory:
7551 * - No closures, instead use prototypical inheritance for API
7552 * - Internal state needs to be stored on scope directly, which means that private state is
7553 * exposed as $$____ properties
7555 * Loop operations are optimized by using while(count--) { ... }
7556 * - this means that in order to keep the same order of execution as addition we have to add
7557 * items to the array at the beginning (shift) instead of at the end (push)
7559 * Child scopes are created and removed often
7560 * - Using an array would be slow since inserts in middle are expensive so we use linked list
7562 * There are few watches then a lot of observers. This is why you don't want the observer to be
7563 * implemented in the same way as watch. Watch requires return of initialization function which
7564 * are expensive to construct.
7570 * @name ng.$rootScopeProvider
7573 * Provider for the $rootScope service.
7578 * @name ng.$rootScopeProvider#digestTtl
7579 * @methodOf ng.$rootScopeProvider
7582 * Sets the number of digest iterations the scope should attempt to execute before giving up and
7583 * assuming that the model is unstable.
7585 * The current default is 10 iterations.
7587 * @param {number} limit The number of digest iterations.
7593 * @name ng.$rootScope
7596 * Every application has a single root {@link ng.$rootScope.Scope scope}.
7597 * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
7598 * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
7600 function $RootScopeProvider(){
7603 this.digestTtl = function(value
) {
7604 if (arguments
.length
) {
7610 this.$get = ['$injector', '$exceptionHandler', '$parse',
7611 function( $injector
, $exceptionHandler
, $parse
) {
7615 * @name ng.$rootScope.Scope
7618 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
7619 * {@link AUTO.$injector $injector}. Child scopes are created using the
7620 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
7621 * compiled HTML template is executed.)
7623 * Here is a simple scope snippet to show how you can interact with the scope.
7625 angular.injector(['ng']).invoke(function($rootScope) {
7626 var scope = $rootScope.$new();
7627 scope.salutation = 'Hello';
7628 scope.name = 'World';
7630 expect(scope.greeting).toEqual(undefined);
7632 scope.$watch('name', function() {
7633 scope.greeting = scope.salutation + ' ' + scope.name + '!';
7634 }); // initialize the watch
7636 expect(scope.greeting).toEqual(undefined);
7637 scope.name = 'Misko';
7638 // still old value, since watches have not been called yet
7639 expect(scope.greeting).toEqual(undefined);
7641 scope.$digest(); // fire all the watches
7642 expect(scope.greeting).toEqual('Hello Misko!');
7647 * A scope can inherit from a parent scope, as in this example:
7649 var parent = $rootScope;
7650 var child = parent.$new();
7652 parent.salutation = "Hello";
7653 child.name = "World";
7654 expect(child.salutation).toEqual('Hello');
7656 child.salutation = "Welcome";
7657 expect(child.salutation).toEqual('Welcome');
7658 expect(parent.salutation).toEqual('Hello');
7662 * @param {Object.<string, function()>=} providers Map of service factory which need to be provided
7663 * for the current scope. Defaults to {@link ng}.
7664 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
7665 * append/override services provided by `providers`. This is handy when unit-testing and having
7666 * the need to override a default service.
7667 * @returns {Object} Newly created scope.
7671 this.$id
= nextUid();
7672 this.$$phase
= this.$parent
= this.$$watchers
=
7673 this.$$nextSibling
= this.$$prevSibling
=
7674 this.$$childHead
= this.$$childTail
= null;
7675 this['this'] = this.$root
= this;
7676 this.$$destroyed
= false;
7677 this.$$asyncQueue
= [];
7678 this.$$listeners
= {};
7679 this.$$isolateBindings
= {};
7684 * @name ng.$rootScope.Scope#$id
7685 * @propertyOf ng.$rootScope.Scope
7686 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
7694 * @name ng.$rootScope.Scope#$new
7695 * @methodOf ng.$rootScope.Scope
7699 * Creates a new child {@link ng.$rootScope.Scope scope}.
7701 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
7702 * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope
7703 * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
7705 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for
7706 * the scope and its child scopes to be permanently detached from the parent and thus stop
7707 * participating in model change detection and listener notification by invoking.
7709 * @param {boolean} isolate if true then the scope does not prototypically inherit from the
7710 * parent scope. The scope is isolated, as it can not see parent scope properties.
7711 * When creating widgets it is useful for the widget to not accidentally read parent
7714 * @returns {Object} The newly created child scope.
7717 $new: function(isolate
) {
7721 if (isFunction(isolate
)) {
7722 // TODO: remove at some point
7723 throw Error('API-CHANGE: Use $controller to instantiate controllers.');
7726 child
= new Scope();
7727 child
.$root
= this.$root
;
7729 Child = function() {}; // should be anonymous; This is so that when the minifier munges
7730 // the name it does not become random set of chars. These will then show up as class
7731 // name in the debugger.
7732 Child
.prototype = this;
7733 child
= new Child();
7734 child
.$id
= nextUid();
7736 child
['this'] = child
;
7737 child
.$$listeners
= {};
7738 child
.$parent
= this;
7739 child
.$$asyncQueue
= [];
7740 child
.$$watchers
= child
.$$nextSibling
= child
.$$childHead
= child
.$$childTail
= null;
7741 child
.$$prevSibling
= this.$$childTail
;
7742 if (this.$$childHead
) {
7743 this.$$childTail
.$$nextSibling
= child
;
7744 this.$$childTail
= child
;
7746 this.$$childHead
= this.$$childTail
= child
;
7753 * @name ng.$rootScope.Scope#$watch
7754 * @methodOf ng.$rootScope.Scope
7758 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
7760 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and
7761 * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()}
7762 * reruns when it detects changes the `watchExpression` can execute multiple times per
7763 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
7764 * - The `listener` is called only when the value from the current `watchExpression` and the
7765 * previous call to `watchExpression` are not equal (with the exception of the initial run,
7766 * see below). The inequality is determined according to
7767 * {@link angular.equals} function. To save the value of the object for later comparison, the
7768 * {@link angular.copy} function is used. It also means that watching complex options will
7769 * have adverse memory and performance implications.
7770 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
7771 * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
7772 * limit is 10 to prevent an infinite loop deadlock.
7775 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
7776 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
7777 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
7778 * detected, be prepared for multiple calls to your listener.)
7780 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
7781 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
7782 * watcher. In rare cases, this is undesirable because the listener is called when the result
7783 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
7784 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
7785 * listener was called due to initialization.
7790 // let's assume that scope was dependency injected as the $rootScope
7791 var scope = $rootScope;
7792 scope.name = 'misko';
7795 expect(scope.counter).toEqual(0);
7796 scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
7797 expect(scope.counter).toEqual(0);
7800 // no variable change
7801 expect(scope.counter).toEqual(0);
7803 scope.name = 'adam';
7805 expect(scope.counter).toEqual(1);
7810 * @param {(function()|string)} watchExpression Expression that is evaluated on each
7811 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a
7812 * call to the `listener`.
7814 * - `string`: Evaluated as {@link guide/expression expression}
7815 * - `function(scope)`: called with current `scope` as a parameter.
7816 * @param {(function()|string)=} listener Callback called whenever the return value of
7817 * the `watchExpression` changes.
7819 * - `string`: Evaluated as {@link guide/expression expression}
7820 * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
7822 * @param {boolean=} objectEquality Compare object for equality rather than for reference.
7823 * @returns {function()} Returns a deregistration function for this listener.
7825 $watch: function(watchExp
, listener
, objectEquality
) {
7827 get = compileToFn(watchExp
, 'watch'),
7828 array
= scope
.$$watchers
,
7834 eq: !!objectEquality
7837 // in the case user pass string, we need to compile it, do we really need this ?
7838 if (!isFunction(listener
)) {
7839 var listenFn
= compileToFn(listener
|| noop
, 'listener');
7840 watcher
.fn = function(newVal
, oldVal
, scope
) {listenFn(scope
);};
7844 array
= scope
.$$watchers
= [];
7846 // we use unshift since we use a while loop in $digest for speed.
7847 // the while loop reads in reverse order.
7848 array
.unshift(watcher
);
7851 arrayRemove(array
, watcher
);
7857 * @name ng.$rootScope.Scope#$digest
7858 * @methodOf ng.$rootScope.Scope
7862 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
7863 * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
7864 * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
7865 * firing. This means that it is possible to get into an infinite loop. This function will throw
7866 * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
7868 * Usually you don't call `$digest()` directly in
7869 * {@link ng.directive:ngController controllers} or in
7870 * {@link ng.$compileProvider#directive directives}.
7871 * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
7872 * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
7874 * If you want to be notified whenever `$digest()` is called,
7875 * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
7876 * with no `listener`.
7878 * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
7884 scope.name = 'misko';
7887 expect(scope.counter).toEqual(0);
7888 scope.$watch('name', function(newValue, oldValue) {
7889 scope.counter = scope.counter + 1;
7891 expect(scope.counter).toEqual(0);
7894 // no variable change
7895 expect(scope.counter).toEqual(0);
7897 scope.name = 'adam';
7899 expect(scope.counter).toEqual(1);
7903 $digest: function() {
7904 var watch
, value
, last
,
7909 next
, current
, target
= this,
7913 beginPhase('$digest');
7919 asyncQueue
= current
.$$asyncQueue
;
7920 while(asyncQueue
.length
) {
7922 current
.$eval(asyncQueue
.shift());
7924 $exceptionHandler(e
);
7927 if ((watchers
= current
.$$watchers
)) {
7928 // process our watches
7929 length
= watchers
.length
;
7932 watch
= watchers
[length
];
7933 // Most common watches are on primitives, in which case we can short
7934 // circuit it with === operator, only when === fails do we use .equals
7935 if ((value
= watch
.get(current
)) !== (last
= watch
.last
) &&
7937 ? equals(value
, last
)
7938 : (typeof value
== 'number' && typeof last
== 'number'
7939 && isNaN(value
) && isNaN(last
)))) {
7941 watch
.last
= watch
.eq
? copy(value
) : value
;
7942 watch
.fn(value
, ((last
=== initWatchVal
) ? value : last
), current
);
7945 if (!watchLog
[logIdx
]) watchLog
[logIdx
] = [];
7946 logMsg
= (isFunction(watch
.exp
))
7947 ? 'fn: ' + (watch
.exp
.name
|| watch
.exp
.toString())
7949 logMsg
+= '; newVal: ' + toJson(value
) + '; oldVal: ' + toJson(last
);
7950 watchLog
[logIdx
].push(logMsg
);
7954 $exceptionHandler(e
);
7959 // Insanity Warning: scope depth-first traversal
7960 // yes, this code is a bit crazy, but it works and we have tests to prove it!
7961 // this piece should be kept in sync with the traversal in $broadcast
7962 if (!(next
= (current
.$$childHead
|| (current
!== target
&& current
.$$nextSibling
)))) {
7963 while(current
!== target
&& !(next
= current
.$$nextSibling
)) {
7964 current
= current
.$parent
;
7967 } while ((current
= next
));
7969 if(dirty
&& !(ttl
--)) {
7971 throw Error(TTL
+ ' $digest() iterations reached. Aborting!\n' +
7972 'Watchers fired in the last 5 iterations: ' + toJson(watchLog
));
7974 } while (dirty
|| asyncQueue
.length
);
7982 * @name ng.$rootScope.Scope#$destroy
7983 * @eventOf ng.$rootScope.Scope
7984 * @eventType broadcast on scope being destroyed
7987 * Broadcasted when a scope and its children are being destroyed.
7992 * @name ng.$rootScope.Scope#$destroy
7993 * @methodOf ng.$rootScope.Scope
7997 * Removes the current scope (and all of its children) from the parent scope. Removal implies
7998 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
7999 * propagate to the current scope and its children. Removal also implies that the current
8000 * scope is eligible for garbage collection.
8002 * The `$destroy()` is usually used by directives such as
8003 * {@link ng.directive:ngRepeat ngRepeat} for managing the
8004 * unrolling of the loop.
8006 * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
8007 * Application code can register a `$destroy` event handler that will give it chance to
8008 * perform any necessary cleanup.
8010 $destroy: function() {
8011 // we can't destroy the root scope or a scope that has been already destroyed
8012 if ($rootScope
== this || this.$$destroyed
) return;
8013 var parent
= this.$parent
;
8015 this.$broadcast('$destroy');
8016 this.$$destroyed
= true;
8018 if (parent
.$$childHead
== this) parent
.$$childHead
= this.$$nextSibling
;
8019 if (parent
.$$childTail
== this) parent
.$$childTail
= this.$$prevSibling
;
8020 if (this.$$prevSibling
) this.$$prevSibling
.$$nextSibling
= this.$$nextSibling
;
8021 if (this.$$nextSibling
) this.$$nextSibling
.$$prevSibling
= this.$$prevSibling
;
8023 // This is bogus code that works around Chrome's GC leak
8024 // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
8025 this.$parent
= this.$$nextSibling
= this.$$prevSibling
= this.$$childHead
=
8026 this.$$childTail
= null;
8031 * @name ng.$rootScope.Scope#$eval
8032 * @methodOf ng.$rootScope.Scope
8036 * Executes the `expression` on the current scope returning the result. Any exceptions in the
8037 * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
8041 var scope = ng.$rootScope.Scope();
8045 expect(scope.$eval('a+b')).toEqual(3);
8046 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
8049 * @param {(string|function())=} expression An angular expression to be executed.
8051 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
8052 * - `function(scope)`: execute the function with the current `scope` parameter.
8054 * @returns {*} The result of evaluating the expression.
8056 $eval: function(expr
, locals
) {
8057 return $parse(expr
)(this, locals
);
8062 * @name ng.$rootScope.Scope#$evalAsync
8063 * @methodOf ng.$rootScope.Scope
8067 * Executes the expression on the current scope at a later point in time.
8069 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
8071 * - it will execute in the current script execution context (before any DOM rendering).
8072 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
8073 * `expression` execution.
8075 * Any exceptions from the execution of the expression are forwarded to the
8076 * {@link ng.$exceptionHandler $exceptionHandler} service.
8078 * @param {(string|function())=} expression An angular expression to be executed.
8080 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
8081 * - `function(scope)`: execute the function with the current `scope` parameter.
8084 $evalAsync: function(expr
) {
8085 this.$$asyncQueue
.push(expr
);
8090 * @name ng.$rootScope.Scope#$apply
8091 * @methodOf ng.$rootScope.Scope
8095 * `$apply()` is used to execute an expression in angular from outside of the angular framework.
8096 * (For example from browser DOM events, setTimeout, XHR or third party libraries).
8097 * Because we are calling into the angular framework we need to perform proper scope life-cycle
8098 * of {@link ng.$exceptionHandler exception handling},
8099 * {@link ng.$rootScope.Scope#$digest executing watches}.
8103 * # Pseudo-Code of `$apply()`
8105 function $apply(expr) {
8109 $exceptionHandler(e);
8117 * Scope's `$apply()` method transitions through the following stages:
8119 * 1. The {@link guide/expression expression} is executed using the
8120 * {@link ng.$rootScope.Scope#$eval $eval()} method.
8121 * 2. Any exceptions from the execution of the expression are forwarded to the
8122 * {@link ng.$exceptionHandler $exceptionHandler} service.
8123 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
8124 * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
8127 * @param {(string|function())=} exp An angular expression to be executed.
8129 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
8130 * - `function(scope)`: execute the function with current `scope` parameter.
8132 * @returns {*} The result of evaluating the expression.
8134 $apply: function(expr
) {
8136 beginPhase('$apply');
8137 return this.$eval(expr
);
8139 $exceptionHandler(e
);
8143 $rootScope
.$digest();
8145 $exceptionHandler(e
);
8153 * @name ng.$rootScope.Scope#$on
8154 * @methodOf ng.$rootScope.Scope
8158 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
8161 * The event listener function format is: `function(event, args...)`. The `event` object
8162 * passed into the listener has the following attributes:
8164 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
8165 * - `currentScope` - `{Scope}`: the current scope which is handling the event.
8166 * - `name` - `{string}`: Name of the event.
8167 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event
8168 * propagation (available only for events that were `$emit`-ed).
8169 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
8170 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
8172 * @param {string} name Event name to listen on.
8173 * @param {function(event, args...)} listener Function to call when the event is emitted.
8174 * @returns {function()} Returns a deregistration function for this listener.
8176 $on: function(name
, listener
) {
8177 var namedListeners
= this.$$listeners
[name
];
8178 if (!namedListeners
) {
8179 this.$$listeners
[name
] = namedListeners
= [];
8181 namedListeners
.push(listener
);
8184 namedListeners
[indexOf(namedListeners
, listener
)] = null;
8191 * @name ng.$rootScope.Scope#$emit
8192 * @methodOf ng.$rootScope.Scope
8196 * Dispatches an event `name` upwards through the scope hierarchy notifying the
8197 * registered {@link ng.$rootScope.Scope#$on} listeners.
8199 * The event life cycle starts at the scope on which `$emit` was called. All
8200 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
8201 * Afterwards, the event traverses upwards toward the root scope and calls all registered
8202 * listeners along the way. The event will stop propagating if one of the listeners cancels it.
8204 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
8205 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
8207 * @param {string} name Event name to emit.
8208 * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
8209 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
8211 $emit: function(name
, args
) {
8215 stopPropagation
= false,
8219 stopPropagation: function() {stopPropagation
= true;},
8220 preventDefault: function() {
8221 event
.defaultPrevented
= true;
8223 defaultPrevented: false
8225 listenerArgs
= concat([event
], arguments
, 1),
8229 namedListeners
= scope
.$$listeners
[name
] || empty
;
8230 event
.currentScope
= scope
;
8231 for (i
=0, length
=namedListeners
.length
; i
<length
; i
++) {
8233 // if listeners were deregistered, defragment the array
8234 if (!namedListeners
[i
]) {
8235 namedListeners
.splice(i
, 1);
8241 namedListeners
[i
].apply(null, listenerArgs
);
8242 if (stopPropagation
) return event
;
8244 $exceptionHandler(e
);
8248 scope
= scope
.$parent
;
8257 * @name ng.$rootScope.Scope#$broadcast
8258 * @methodOf ng.$rootScope.Scope
8262 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
8263 * registered {@link ng.$rootScope.Scope#$on} listeners.
8265 * The event life cycle starts at the scope on which `$broadcast` was called. All
8266 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
8267 * Afterwards, the event propagates to all direct and indirect scopes of the current scope and
8268 * calls all registered listeners along the way. The event cannot be canceled.
8270 * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
8271 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
8273 * @param {string} name Event name to broadcast.
8274 * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
8275 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
8277 $broadcast: function(name
, args
) {
8283 targetScope: target
,
8284 preventDefault: function() {
8285 event
.defaultPrevented
= true;
8287 defaultPrevented: false
8289 listenerArgs
= concat([event
], arguments
, 1),
8290 listeners
, i
, length
;
8292 //down while you can, then up and next sibling or up and next sibling until back at root
8295 event
.currentScope
= current
;
8296 listeners
= current
.$$listeners
[name
] || [];
8297 for (i
=0, length
= listeners
.length
; i
<length
; i
++) {
8298 // if listeners were deregistered, defragment the array
8299 if (!listeners
[i
]) {
8300 listeners
.splice(i
, 1);
8307 listeners
[i
].apply(null, listenerArgs
);
8309 $exceptionHandler(e
);
8313 // Insanity Warning: scope depth-first traversal
8314 // yes, this code is a bit crazy, but it works and we have tests to prove it!
8315 // this piece should be kept in sync with the traversal in $digest
8316 if (!(next
= (current
.$$childHead
|| (current
!== target
&& current
.$$nextSibling
)))) {
8317 while(current
!== target
&& !(next
= current
.$$nextSibling
)) {
8318 current
= current
.$parent
;
8321 } while ((current
= next
));
8327 var $rootScope
= new Scope();
8332 function beginPhase(phase
) {
8333 if ($rootScope
.$$phase
) {
8334 throw Error($rootScope
.$$phase
+ ' already in progress');
8337 $rootScope
.$$phase
= phase
;
8340 function clearPhase() {
8341 $rootScope
.$$phase
= null;
8344 function compileToFn(exp
, name
) {
8345 var fn
= $parse(exp
);
8346 assertArgFn(fn
, name
);
8351 * function used as an initial value for watchers.
8352 * because it's unique we can easily tell it apart from other values
8354 function initWatchVal() {}
8359 * !!! This is an undocumented "private" service !!!
8364 * @property {boolean} history Does the browser support html5 history api ?
8365 * @property {boolean} hashchange Does the browser support hashchange event ?
8368 * This is very simple implementation of testing browser's features.
8370 function $SnifferProvider() {
8371 this.$get = ['$window', function($window
) {
8372 var eventSupport
= {},
8373 android
= int((/android (\d
+)/.exec(lowercase($window
.navigator
.userAgent
)) || [])[1]);
8376 // Android has history.pushState, but it does not update location correctly
8377 // so let's not use the history API at all.
8378 // http://code.google.com/p/android/issues/detail?id=17471
8379 // https://github.com/angular/angular.js/issues/904
8380 history: !!($window
.history
&& $window
.history
.pushState
&& !(android
< 4)),
8381 hashchange: 'onhashchange' in $window
&&
8382 // IE8 compatible mode lies
8383 (!$window
.document
.documentMode
|| $window
.document
.documentMode
> 7),
8384 hasEvent: function(event
) {
8385 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
8386 // it. In particular the event is not fired when backspace or delete key are pressed or
8387 // when cut operation is performed.
8388 if (event
== 'input' && msie
== 9) return false;
8390 if (isUndefined(eventSupport
[event
])) {
8391 var divElm
= $window
.document
.createElement('div');
8392 eventSupport
[event
] = 'on' + event
in divElm
;
8395 return eventSupport
[event
];
8397 // TODO(i): currently there is no way to feature detect CSP without triggering alerts
8408 * A reference to the browser's `window` object. While `window`
8409 * is globally available in JavaScript, it causes testability problems, because
8410 * it is a global variable. In angular we always refer to it through the
8411 * `$window` service, so it may be overriden, removed or mocked for testing.
8413 * All expressions are evaluated with respect to current scope so they don't
8414 * suffer from window globality.
8420 function Ctrl($scope, $window) {
8421 $scope.$window = $window;
8422 $scope.greeting = 'Hello, World!';
8425 <div ng-controller="Ctrl">
8426 <input type="text" ng-model="greeting" />
8427 <button ng-click="$window.alert(greeting)">ALERT</button>
8431 it('should display the greeting in the input box', function() {
8432 input('greeting').enter('Hello, E2E Tests');
8433 // If we click the button it will block the test runner
8434 // element(':button').click();
8439 function $WindowProvider(){
8440 this.$get = valueFn(window
);
8444 * Parse headers into key value object
8446 * @param {string} headers Raw headers as a string
8447 * @returns {Object} Parsed headers as key value object
8449 function parseHeaders(headers
) {
8450 var parsed
= {}, key
, val
, i
;
8452 if (!headers
) return parsed
;
8454 forEach(headers
.split('\n'), function(line
) {
8455 i
= line
.indexOf(':');
8456 key
= lowercase(trim(line
.substr(0, i
)));
8457 val
= trim(line
.substr(i
+ 1));
8461 parsed
[key
] += ', ' + val
;
8473 * Returns a function that provides access to parsed headers.
8475 * Headers are lazy parsed when first requested.
8478 * @param {(string|Object)} headers Headers to provide access to.
8479 * @returns {function(string=)} Returns a getter function which if called with:
8481 * - if called with single an argument returns a single header value or null
8482 * - if called with no arguments returns an object containing all headers.
8484 function headersGetter(headers
) {
8485 var headersObj
= isObject(headers
) ? headers : undefined;
8487 return function(name
) {
8488 if (!headersObj
) headersObj
= parseHeaders(headers
);
8491 return headersObj
[lowercase(name
)] || null;
8500 * Chain all given functions
8502 * This function is used for both request and response transforming
8504 * @param {*} data Data to transform.
8505 * @param {function(string=)} headers Http headers getter fn.
8506 * @param {(function|Array.<function>)} fns Function or an array of functions.
8507 * @returns {*} Transformed data.
8509 function transformData(data
, headers
, fns
) {
8510 if (isFunction(fns
))
8511 return fns(data
, headers
);
8513 forEach(fns
, function(fn
) {
8514 data
= fn(data
, headers
);
8521 function isSuccess(status
) {
8522 return 200 <= status
&& status
< 300;
8526 function $HttpProvider() {
8527 var JSON_START
= /^\s*(\[|\{[^\{])/,
8528 JSON_END
= /[\}\]]\s*$/,
8529 PROTECTION_PREFIX
= /^\)\]\}',?\n/;
8531 var $config
= this.defaults
= {
8532 // transform incoming response data
8533 transformResponse: [function(data
) {
8534 if (isString(data
)) {
8535 // strip json vulnerability protection prefix
8536 data
= data
.replace(PROTECTION_PREFIX
, '');
8537 if (JSON_START
.test(data
) && JSON_END
.test(data
))
8538 data
= fromJson(data
, true);
8543 // transform outgoing request data
8544 transformRequest: [function(d
) {
8545 return isObject(d
) && !isFile(d
) ? toJson(d
) : d
;
8551 'Accept': 'application/json, text/plain, */*',
8552 'X-Requested-With': 'XMLHttpRequest'
8554 post: {'Content-Type': 'application/json;charset=utf-8'},
8555 put: {'Content-Type': 'application/json;charset=utf-8'}
8559 var providerResponseInterceptors
= this.responseInterceptors
= [];
8561 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
8562 function($httpBackend
, $browser
, $cacheFactory
, $rootScope
, $q
, $injector
) {
8564 var defaultCache
= $cacheFactory('$http'),
8565 responseInterceptors
= [];
8567 forEach(providerResponseInterceptors
, function(interceptor
) {
8568 responseInterceptors
.push(
8569 isString(interceptor
)
8570 ? $injector
.get(interceptor
)
8571 : $injector
.invoke(interceptor
)
8579 * @requires $httpBackend
8580 * @requires $browser
8581 * @requires $cacheFactory
8582 * @requires $rootScope
8584 * @requires $injector
8587 * The `$http` service is a core Angular service that facilitates communication with the remote
8588 * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest
8589 * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
8591 * For unit testing applications that use `$http` service, see
8592 * {@link ngMock.$httpBackend $httpBackend mock}.
8594 * For a higher level of abstraction, please check out the {@link ngResource.$resource
8595 * $resource} service.
8597 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
8598 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
8599 * it is important to familiarize yourself with these APIs and the guarantees they provide.
8603 * The `$http` service is a function which takes a single argument — a configuration object —
8604 * that is used to generate an HTTP request and returns a {@link ng.$q promise}
8605 * with two $http specific methods: `success` and `error`.
8608 * $http({method: 'GET', url: '/someUrl'}).
8609 * success(function(data, status, headers, config) {
8610 * // this callback will be called asynchronously
8611 * // when the response is available
8613 * error(function(data, status, headers, config) {
8614 * // called asynchronously if an error occurs
8615 * // or server returns response with an error status.
8619 * Since the returned value of calling the $http function is a `promise`, you can also use
8620 * the `then` method to register callbacks, and these callbacks will receive a single argument –
8621 * an object representing the response. See the API signature and type info below for more
8624 * A response status code between 200 and 299 is considered a success status and
8625 * will result in the success callback being called. Note that if the response is a redirect,
8626 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
8627 * called for such responses.
8629 * # Shortcut methods
8631 * Since all invocations of the $http service require passing in an HTTP method and URL, and
8632 * POST/PUT requests require request data to be provided as well, shortcut methods
8636 * $http.get('/someUrl').success(successCallback);
8637 * $http.post('/someUrl', data).success(successCallback);
8640 * Complete list of shortcut methods:
8642 * - {@link ng.$http#get $http.get}
8643 * - {@link ng.$http#head $http.head}
8644 * - {@link ng.$http#post $http.post}
8645 * - {@link ng.$http#put $http.put}
8646 * - {@link ng.$http#delete $http.delete}
8647 * - {@link ng.$http#jsonp $http.jsonp}
8650 * # Setting HTTP Headers
8652 * The $http service will automatically add certain HTTP headers to all requests. These defaults
8653 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
8654 * object, which currently contains this default configuration:
8656 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
8657 * - `Accept: application/json, text/plain, * / *`
8658 * - `X-Requested-With: XMLHttpRequest`
8659 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
8660 * - `Content-Type: application/json`
8661 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
8662 * - `Content-Type: application/json`
8664 * To add or overwrite these defaults, simply add or remove a property from these configuration
8665 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
8666 * with the lowercased HTTP method name as the key, e.g.
8667 * `$httpProvider.defaults.headers.get['My-Header']='value'`.
8669 * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same
8673 * # Transforming Requests and Responses
8675 * Both requests and responses can be transformed using transform functions. By default, Angular
8676 * applies these transformations:
8678 * Request transformations:
8680 * - If the `data` property of the request configuration object contains an object, serialize it into
8683 * Response transformations:
8685 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
8686 * - If JSON response is detected, deserialize it using a JSON parser.
8688 * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and
8689 * `$httpProvider.defaults.transformResponse` properties. These properties are by default an
8690 * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the
8691 * transformation chain. You can also decide to completely override any default transformations by assigning your
8692 * transformation functions to these properties directly without the array wrapper.
8694 * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
8695 * `transformResponse` properties of the configuration object passed into `$http`.
8700 * To enable caching, set the configuration property `cache` to `true`. When the cache is
8701 * enabled, `$http` stores the response from the server in local cache. Next time the
8702 * response is served from the cache without sending a request to the server.
8704 * Note that even if the response is served from cache, delivery of the data is asynchronous in
8705 * the same way that real requests are.
8707 * If there are multiple GET requests for the same URL that should be cached using the same
8708 * cache, but the cache is not populated yet, only one request to the server will be made and
8709 * the remaining requests will be fulfilled using the response from the first request.
8712 * # Response interceptors
8714 * Before you start creating interceptors, be sure to understand the
8715 * {@link ng.$q $q and deferred/promise APIs}.
8717 * For purposes of global error handling, authentication or any kind of synchronous or
8718 * asynchronous preprocessing of received responses, it is desirable to be able to intercept
8719 * responses for http requests before they are handed over to the application code that
8720 * initiated these requests. The response interceptors leverage the {@link ng.$q
8721 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
8723 * The interceptors are service factories that are registered with the $httpProvider by
8724 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
8725 * injected with dependencies (if specified) and returns the interceptor — a function that
8726 * takes a {@link ng.$q promise} and returns the original or a new promise.
8729 * // register the interceptor as a service
8730 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
8731 * return function(promise) {
8732 * return promise.then(function(response) {
8733 * // do something on success
8734 * }, function(response) {
8735 * // do something on error
8736 * if (canRecover(response)) {
8737 * return responseOrNewPromise
8739 * return $q.reject(response);
8744 * $httpProvider.responseInterceptors.push('myHttpInterceptor');
8747 * // register the interceptor via an anonymous factory
8748 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
8749 * return function(promise) {
8756 * # Security Considerations
8758 * When designing web applications, consider security threats from:
8760 * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
8761 * JSON vulnerability}
8762 * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
8764 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
8765 * pre-configured with strategies that address these issues, but for this to work backend server
8766 * cooperation is required.
8768 * ## JSON Vulnerability Protection
8770 * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
8771 * JSON vulnerability} allows third party website to turn your JSON resource URL into
8772 * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To
8773 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
8774 * Angular will automatically strip the prefix before processing it as JSON.
8776 * For example if your server needs to return:
8781 * which is vulnerable to attack, your server can return:
8787 * Angular will strip the prefix, before processing the JSON.
8790 * ## Cross Site Request Forgery (XSRF) Protection
8792 * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
8793 * an unauthorized site can gain your user's private data. Angular provides a mechanism
8794 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
8795 * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
8796 * runs on your domain could read the cookie, your server can be assured that the XHR came from
8797 * JavaScript running on your domain.
8799 * To take advantage of this, your server needs to set a token in a JavaScript readable session
8800 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
8801 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
8802 * that only JavaScript running on your domain could have sent the request. The token must be
8803 * unique for each user and must be verifiable by the server (to prevent the JavaScript from making
8804 * up its own tokens). We recommend that the token is a digest of your site's authentication
8805 * cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} for added security.
8808 * @param {object} config Object describing the request to be made and how it should be
8809 * processed. The object has following properties:
8811 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
8812 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
8813 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned to
8814 * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
8815 * - **data** – `{string|Object}` – Data to be sent as the request message data.
8816 * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
8817 * - **transformRequest** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
8818 * transform function or an array of such functions. The transform function takes the http
8819 * request body and headers and returns its transformed (typically serialized) version.
8820 * - **transformResponse** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
8821 * transform function or an array of such functions. The transform function takes the http
8822 * response body and headers and returns its transformed (typically deserialized) version.
8823 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
8824 * GET request, otherwise if a cache instance built with
8825 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
8827 * - **timeout** – `{number}` – timeout in milliseconds.
8828 * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
8829 * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
8830 * requests with credentials} for more information.
8832 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
8833 * standard `then` method and two http specific methods: `success` and `error`. The `then`
8834 * method takes two arguments a success and an error callback which will be called with a
8835 * response object. The `success` and `error` methods take a single argument - a function that
8836 * will be called when the request succeeds or fails respectively. The arguments passed into
8837 * these functions are destructured representation of the response object passed into the
8838 * `then` method. The response object has these properties:
8840 * - **data** – `{string|Object}` – The response body transformed with the transform functions.
8841 * - **status** – `{number}` – HTTP status code of the response.
8842 * - **headers** – `{function([headerName])}` – Header getter function.
8843 * - **config** – `{Object}` – The configuration object that was used to generate the request.
8845 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
8846 * requests. This is primarily meant to be used for debugging purposes.
8851 <file name="index.html">
8852 <div ng-controller="FetchCtrl">
8853 <select ng-model="method">
8854 <option>GET</option>
8855 <option>JSONP</option>
8857 <input type="text" ng-model="url" size="80"/>
8858 <button ng-click="fetch()">fetch</button><br>
8859 <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
8860 <button ng-click="updateModel('JSONP', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
8861 <button ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
8862 <pre>http status code: {{status}}</pre>
8863 <pre>http response data: {{data}}</pre>
8866 <file name="script.js">
8867 function FetchCtrl($scope, $http, $templateCache) {
8868 $scope.method = 'GET';
8869 $scope.url = 'http-hello.html';
8871 $scope.fetch = function() {
8873 $scope.response = null;
8875 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
8876 success(function(data, status) {
8877 $scope.status = status;
8880 error(function(data, status) {
8881 $scope.data = data || "Request failed";
8882 $scope.status = status;
8886 $scope.updateModel = function(method, url) {
8887 $scope.method = method;
8892 <file name="http-hello.html">
8895 <file name="scenario.js">
8896 it('should make an xhr GET request', function() {
8897 element(':button:contains("Sample GET")').click();
8898 element(':button:contains("fetch")').click();
8899 expect(binding('status')).toBe('200');
8900 expect(binding('data')).toMatch(/Hello, \$http!/);
8903 it('should make a JSONP request to angularjs.org', function() {
8904 element(':button:contains("Sample JSONP")').click();
8905 element(':button:contains("fetch")').click();
8906 expect(binding('status')).toBe('200');
8907 expect(binding('data')).toMatch(/Super Hero!/);
8910 it('should make JSONP request to invalid URL and invoke the error handler',
8912 element(':button:contains("Invalid JSONP")').click();
8913 element(':button:contains("fetch")').click();
8914 expect(binding('status')).toBe('0');
8915 expect(binding('data')).toBe('Request failed');
8920 function $http(config
) {
8921 config
.method
= uppercase(config
.method
);
8923 var reqTransformFn
= config
.transformRequest
|| $config
.transformRequest
,
8924 respTransformFn
= config
.transformResponse
|| $config
.transformResponse
,
8925 defHeaders
= $config
.headers
,
8926 reqHeaders
= extend({'X-XSRF-TOKEN': $browser
.cookies()['XSRF-TOKEN']},
8927 defHeaders
.common
, defHeaders
[lowercase(config
.method
)], config
.headers
),
8928 reqData
= transformData(config
.data
, headersGetter(reqHeaders
), reqTransformFn
),
8931 // strip content-type if data is undefined
8932 if (isUndefined(config
.data
)) {
8933 delete reqHeaders
['Content-Type'];
8937 promise
= sendReq(config
, reqData
, reqHeaders
);
8940 // transform future response
8941 promise
= promise
.then(transformResponse
, transformResponse
);
8943 // apply interceptors
8944 forEach(responseInterceptors
, function(interceptor
) {
8945 promise
= interceptor(promise
);
8948 promise
.success = function(fn
) {
8949 promise
.then(function(response
) {
8950 fn(response
.data
, response
.status
, response
.headers
, config
);
8955 promise
.error = function(fn
) {
8956 promise
.then(null, function(response
) {
8957 fn(response
.data
, response
.status
, response
.headers
, config
);
8964 function transformResponse(response
) {
8965 // make a copy since the response must be cacheable
8966 var resp
= extend({}, response
, {
8967 data: transformData(response
.data
, response
.headers
, respTransformFn
)
8969 return (isSuccess(response
.status
))
8975 $http
.pendingRequests
= [];
8979 * @name ng.$http#get
8980 * @methodOf ng.$http
8983 * Shortcut method to perform `GET` request.
8985 * @param {string} url Relative or absolute URL specifying the destination of the request
8986 * @param {Object=} config Optional configuration object
8987 * @returns {HttpPromise} Future object
8992 * @name ng.$http#delete
8993 * @methodOf ng.$http
8996 * Shortcut method to perform `DELETE` request.
8998 * @param {string} url Relative or absolute URL specifying the destination of the request
8999 * @param {Object=} config Optional configuration object
9000 * @returns {HttpPromise} Future object
9005 * @name ng.$http#head
9006 * @methodOf ng.$http
9009 * Shortcut method to perform `HEAD` request.
9011 * @param {string} url Relative or absolute URL specifying the destination of the request
9012 * @param {Object=} config Optional configuration object
9013 * @returns {HttpPromise} Future object
9018 * @name ng.$http#jsonp
9019 * @methodOf ng.$http
9022 * Shortcut method to perform `JSONP` request.
9024 * @param {string} url Relative or absolute URL specifying the destination of the request.
9025 * Should contain `JSON_CALLBACK` string.
9026 * @param {Object=} config Optional configuration object
9027 * @returns {HttpPromise} Future object
9029 createShortMethods('get', 'delete', 'head', 'jsonp');
9033 * @name ng.$http#post
9034 * @methodOf ng.$http
9037 * Shortcut method to perform `POST` request.
9039 * @param {string} url Relative or absolute URL specifying the destination of the request
9040 * @param {*} data Request content
9041 * @param {Object=} config Optional configuration object
9042 * @returns {HttpPromise} Future object
9047 * @name ng.$http#put
9048 * @methodOf ng.$http
9051 * Shortcut method to perform `PUT` request.
9053 * @param {string} url Relative or absolute URL specifying the destination of the request
9054 * @param {*} data Request content
9055 * @param {Object=} config Optional configuration object
9056 * @returns {HttpPromise} Future object
9058 createShortMethodsWithData('post', 'put');
9062 * @name ng.$http#defaults
9063 * @propertyOf ng.$http
9066 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
9067 * default headers as well as request and response transformations.
9069 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
9071 $http
.defaults
= $config
;
9077 function createShortMethods(names
) {
9078 forEach(arguments
, function(name
) {
9079 $http
[name
] = function(url
, config
) {
9080 return $http(extend(config
|| {}, {
9089 function createShortMethodsWithData(name
) {
9090 forEach(arguments
, function(name
) {
9091 $http
[name
] = function(url
, data
, config
) {
9092 return $http(extend(config
|| {}, {
9103 * Makes the request.
9105 * !!! ACCESSES CLOSURE VARS:
9106 * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
9108 function sendReq(config
, reqData
, reqHeaders
) {
9109 var deferred
= $q
.defer(),
9110 promise
= deferred
.promise
,
9113 url
= buildUrl(config
.url
, config
.params
);
9115 $http
.pendingRequests
.push(config
);
9116 promise
.then(removePendingReq
, removePendingReq
);
9119 if (config
.cache
&& config
.method
== 'GET') {
9120 cache
= isObject(config
.cache
) ? config
.cache : defaultCache
;
9124 cachedResp
= cache
.get(url
);
9126 if (cachedResp
.then
) {
9127 // cached request has already been sent, but there is no response yet
9128 cachedResp
.then(removePendingReq
, removePendingReq
);
9131 // serving from cache
9132 if (isArray(cachedResp
)) {
9133 resolvePromise(cachedResp
[1], cachedResp
[0], copy(cachedResp
[2]));
9135 resolvePromise(cachedResp
, 200, {});
9139 // put the promise for the non-transformed response into cache as a placeholder
9140 cache
.put(url
, promise
);
9144 // if we won't have the response in cache, send the request to the backend
9146 $httpBackend(config
.method
, url
, reqData
, done
, reqHeaders
, config
.timeout
,
9147 config
.withCredentials
);
9154 * Callback registered to $httpBackend():
9155 * - caches the response if desired
9156 * - resolves the raw $http promise
9159 function done(status
, response
, headersString
) {
9161 if (isSuccess(status
)) {
9162 cache
.put(url
, [status
, response
, parseHeaders(headersString
)]);
9164 // remove promise from the cache
9169 resolvePromise(response
, status
, headersString
);
9170 $rootScope
.$apply();
9175 * Resolves the raw $http promise.
9177 function resolvePromise(response
, status
, headers
) {
9178 // normalize internal statuses to 0
9179 status
= Math
.max(status
, 0);
9181 (isSuccess(status
) ? deferred
.resolve : deferred
.reject
)({
9184 headers: headersGetter(headers
),
9190 function removePendingReq() {
9191 var idx
= indexOf($http
.pendingRequests
, config
);
9192 if (idx
!== -1) $http
.pendingRequests
.splice(idx
, 1);
9197 function buildUrl(url
, params
) {
9198 if (!params
) return url
;
9200 forEachSorted(params
, function(value
, key
) {
9201 if (value
== null || value
== undefined) return;
9202 if (isObject(value
)) {
9203 value
= toJson(value
);
9205 parts
.push(encodeURIComponent(key
) + '=' + encodeURIComponent(value
));
9207 return url
+ ((url
.indexOf('?') == -1) ? '?' : '&') + parts
.join('&');
9214 var XHR
= window
.XMLHttpRequest
|| function() {
9215 try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1
) {}
9216 try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2
) {}
9217 try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3
) {}
9218 throw new Error("This browser does not support XMLHttpRequest.");
9224 * @name ng.$httpBackend
9225 * @requires $browser
9227 * @requires $document
9230 * HTTP backend used by the {@link ng.$http service} that delegates to
9231 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
9233 * You should never need to use this service directly, instead use the higher-level abstractions:
9234 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
9236 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
9237 * $httpBackend} which can be trained with responses.
9239 function $HttpBackendProvider() {
9240 this.$get = ['$browser', '$window', '$document', function($browser
, $window
, $document
) {
9241 return createHttpBackend($browser
, XHR
, $browser
.defer
, $window
.angular
.callbacks
,
9242 $document
[0], $window
.location
.protocol
.replace(':', ''));
9246 function createHttpBackend($browser
, XHR
, $browserDefer
, callbacks
, rawDocument
, locationProtocol
) {
9247 // TODO(vojta): fix the signature
9248 return function(method
, url
, post
, callback
, headers
, timeout
, withCredentials
) {
9249 $browser
.$$incOutstandingRequestCount();
9250 url
= url
|| $browser
.url();
9252 if (lowercase(method
) == 'jsonp') {
9253 var callbackId
= '_' + (callbacks
.counter
++).toString(36);
9254 callbacks
[callbackId
] = function(data
) {
9255 callbacks
[callbackId
].data
= data
;
9258 jsonpReq(url
.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId
),
9260 if (callbacks
[callbackId
].data
) {
9261 completeRequest(callback
, 200, callbacks
[callbackId
].data
);
9263 completeRequest(callback
, -2);
9265 delete callbacks
[callbackId
];
9268 var xhr
= new XHR();
9269 xhr
.open(method
, url
, true);
9270 forEach(headers
, function(value
, key
) {
9271 if (value
) xhr
.setRequestHeader(key
, value
);
9276 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
9277 // response is in the cache. the promise api will ensure that to the app code the api is
9279 xhr
.onreadystatechange = function() {
9280 if (xhr
.readyState
== 4) {
9281 var responseHeaders
= xhr
.getAllResponseHeaders();
9283 // TODO(vojta): remove once Firefox 21 gets released.
9284 // begin: workaround to overcome Firefox CORS http response headers bug
9285 // https://bugzilla.mozilla.org/show_bug.cgi?id=608735
9286 // Firefox already patched in nightly. Should land in Firefox 21.
9288 // CORS "simple response headers" http://www.w3.org/TR/cors/
9290 simpleHeaders
= ["Cache-Control", "Content-Language", "Content-Type",
9291 "Expires", "Last-Modified", "Pragma"];
9292 if (!responseHeaders
) {
9293 responseHeaders
= "";
9294 forEach(simpleHeaders
, function (header
) {
9295 var value
= xhr
.getResponseHeader(header
);
9297 responseHeaders
+= header
+ ": " + value
+ "\n";
9301 // end of the workaround.
9303 completeRequest(callback
, status
|| xhr
.status
, xhr
.responseText
,
9308 if (withCredentials
) {
9309 xhr
.withCredentials
= true;
9312 xhr
.send(post
|| '');
9315 $browserDefer(function() {
9323 function completeRequest(callback
, status
, response
, headersString
) {
9324 // URL_MATCH is defined in src/service/location.js
9325 var protocol
= (url
.match(URL_MATCH
) || ['', locationProtocol
])[1];
9327 // fix status code for file protocol (it's always 0)
9328 status
= (protocol
== 'file') ? (response
? 200 : 404) : status
;
9330 // normalize IE bug (http://bugs.jquery.com/ticket/1450)
9331 status
= status
== 1223 ? 204 : status
;
9333 callback(status
, response
, headersString
);
9334 $browser
.$$completeOutstandingRequest(noop
);
9338 function jsonpReq(url
, done
) {
9339 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
9340 // - fetches local scripts via XHR and evals them
9341 // - adds and immediately removes script elements from the document
9342 var script
= rawDocument
.createElement('script'),
9343 doneWrapper = function() {
9344 rawDocument
.body
.removeChild(script
);
9348 script
.type
= 'text/javascript';
9352 script
.onreadystatechange = function() {
9353 if (/loaded|complete/.test(script
.readyState
)) doneWrapper();
9356 script
.onload
= script
.onerror
= doneWrapper
;
9359 rawDocument
.body
.appendChild(script
);
9368 * $locale service provides localization rules for various Angular components. As of right now the
9369 * only public api is:
9371 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
9373 function $LocaleProvider(){
9374 this.$get = function() {
9382 { // Decimal Pattern
9392 },{ //Currency Pattern
9408 MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
9410 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
9411 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
9412 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
9414 medium: 'MMM d, y h:mm:ss a',
9415 short: 'M/d/yy h:mm a',
9416 fullDate: 'EEEE, MMMM d, y',
9417 longDate: 'MMMM d, y',
9418 mediumDate: 'MMM d, y',
9419 shortDate: 'M/d/yy',
9420 mediumTime: 'h:mm:ss a',
9424 pluralCat: function(num
) {
9434 function $TimeoutProvider() {
9435 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
9436 function($rootScope
, $browser
, $q
, $exceptionHandler
) {
9443 * @requires $browser
9446 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
9447 * block and delegates any exceptions to
9448 * {@link ng.$exceptionHandler $exceptionHandler} service.
9450 * The return value of registering a timeout function is a promise, which will be resolved when
9451 * the timeout is reached and the timeout function is executed.
9453 * To cancel a timeout request, call `$timeout.cancel(promise)`.
9455 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
9456 * synchronously flush the queue of deferred functions.
9458 * @param {function()} fn A function, whose execution should be delayed.
9459 * @param {number=} [delay=0] Delay in milliseconds.
9460 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
9461 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
9462 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
9463 * promise will be resolved with is the return value of the `fn` function.
9465 function timeout(fn
, delay
, invokeApply
) {
9466 var deferred
= $q
.defer(),
9467 promise
= deferred
.promise
,
9468 skipApply
= (isDefined(invokeApply
) && !invokeApply
),
9471 timeoutId
= $browser
.defer(function() {
9473 deferred
.resolve(fn());
9476 $exceptionHandler(e
);
9479 if (!skipApply
) $rootScope
.$apply();
9482 cleanup = function() {
9483 delete deferreds
[promise
.$$timeoutId
];
9486 promise
.$$timeoutId
= timeoutId
;
9487 deferreds
[timeoutId
] = deferred
;
9488 promise
.then(cleanup
, cleanup
);
9496 * @name ng.$timeout#cancel
9497 * @methodOf ng.$timeout
9500 * Cancels a task associated with the `promise`. As a result of this, the promise will be
9501 * resolved with a rejection.
9503 * @param {Promise=} promise Promise returned by the `$timeout` function.
9504 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
9507 timeout
.cancel = function(promise
) {
9508 if (promise
&& promise
.$$timeoutId
in deferreds
) {
9509 deferreds
[promise
.$$timeoutId
].reject('canceled');
9510 return $browser
.defer
.cancel(promise
.$$timeoutId
);
9521 * @name ng.$filterProvider
9524 * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
9525 * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
9526 * responsible for creating a filter function.
9529 * // Filter registration
9530 * function MyModule($provide, $filterProvider) {
9531 * // create a service to demonstrate injection (not always needed)
9532 * $provide.value('greet', function(name){
9533 * return 'Hello ' + name + '!';
9536 * // register a filter factory which uses the
9537 * // greet service to demonstrate DI.
9538 * $filterProvider.register('greet', function(greet){
9539 * // return the filter function which uses the greet service
9540 * // to generate salutation
9541 * return function(text) {
9542 * // filters need to be forgiving so check input validity
9543 * return text && greet(text) || text;
9549 * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
9551 * it('should be the same instance', inject(
9552 * function($filterProvider) {
9553 * $filterProvider.register('reverse', function(){
9557 * function($filter, reverseFilter) {
9558 * expect($filter('reverse')).toBe(reverseFilter);
9563 * For more information about how angular filters work, and how to create your own filters, see
9564 * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
9569 * @name ng.$filterProvider#register
9570 * @methodOf ng.$filterProvider
9572 * Register filter factory function.
9574 * @param {String} name Name of the filter.
9575 * @param {function} fn The filter factory function which is injectable.
9584 * Filters are used for formatting data displayed to the user.
9586 * The general syntax in templates is as follows:
9588 * {{ expression [| filter_name[:parameter_value] ... ] }}
9590 * @param {String} name Name of the filter function to retrieve
9591 * @return {Function} the filter function
9593 $FilterProvider
.$inject
= ['$provide'];
9594 function $FilterProvider($provide
) {
9595 var suffix
= 'Filter';
9597 function register(name
, factory
) {
9598 return $provide
.factory(name
+ suffix
, factory
);
9600 this.register
= register
;
9602 this.$get = ['$injector', function($injector
) {
9603 return function(name
) {
9604 return $injector
.get(name
+ suffix
);
9608 ////////////////////////////////////////
9610 register('currency', currencyFilter
);
9611 register('date', dateFilter
);
9612 register('filter', filterFilter
);
9613 register('json', jsonFilter
);
9614 register('limitTo', limitToFilter
);
9615 register('lowercase', lowercaseFilter
);
9616 register('number', numberFilter
);
9617 register('orderBy', orderByFilter
);
9618 register('uppercase', uppercaseFilter
);
9623 * @name ng.filter:filter
9627 * Selects a subset of items from `array` and returns it as a new array.
9629 * Note: This function is used to augment the `Array` type in Angular expressions. See
9630 * {@link ng.$filter} for more information about Angular arrays.
9632 * @param {Array} array The source array.
9633 * @param {string|Object|function()} expression The predicate to be used for selecting items from
9638 * - `string`: Predicate that results in a substring match using the value of `expression`
9639 * string. All strings or objects with string properties in `array` that contain this string
9640 * will be returned. The predicate can be negated by prefixing the string with `!`.
9642 * - `Object`: A pattern object can be used to filter specific properties on objects contained
9643 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
9644 * which have property `name` containing "M" and property `phone` containing "1". A special
9645 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
9646 * property of the object. That's equivalent to the simple substring match with a `string`
9647 * as described above.
9649 * - `function`: A predicate function can be used to write arbitrary filters. The function is
9650 * called for each element of `array`. The final result is an array of those elements that
9651 * the predicate returned true for.
9656 <div ng-init="friends = [{name:'John', phone:'555-1276'},
9657 {name:'Mary', phone:'800-BIG-MARY'},
9658 {name:'Mike', phone:'555-4321'},
9659 {name:'Adam', phone:'555-5678'},
9660 {name:'Julie', phone:'555-8765'}]"></div>
9662 Search: <input ng-model="searchText">
9663 <table id="searchTextResults">
9664 <tr><th>Name</th><th>Phone</th></tr>
9665 <tr ng-repeat="friend in friends | filter:searchText">
9666 <td>{{friend.name}}</td>
9667 <td>{{friend.phone}}</td>
9671 Any: <input ng-model="search.$"> <br>
9672 Name only <input ng-model="search.name"><br>
9673 Phone only <input ng-model="search.phone"><br>
9674 <table id="searchObjResults">
9675 <tr><th>Name</th><th>Phone</th></tr>
9676 <tr ng-repeat="friend in friends | filter:search">
9677 <td>{{friend.name}}</td>
9678 <td>{{friend.phone}}</td>
9683 it('should search across all fields when filtering with a string', function() {
9684 input('searchText').enter('m');
9685 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
9686 toEqual(['Mary', 'Mike', 'Adam']);
9688 input('searchText').enter('76');
9689 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
9690 toEqual(['John', 'Julie']);
9693 it('should search in specific fields when filtering with a predicate object', function() {
9694 input('search.$').enter('i');
9695 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
9696 toEqual(['Mary', 'Mike', 'Julie']);
9701 function filterFilter() {
9702 return function(array
, expression
) {
9703 if (!isArray(array
)) return array
;
9704 var predicates
= [];
9705 predicates
.check = function(value
) {
9706 for (var j
= 0; j
< predicates
.length
; j
++) {
9707 if(!predicates
[j
](value
)) {
9713 var search = function(obj
, text
){
9714 if (text
.charAt(0) === '!') {
9715 return !search(obj
, text
.substr(1));
9717 switch (typeof obj
) {
9721 return ('' + obj
).toLowerCase().indexOf(text
) > -1;
9723 for ( var objKey
in obj
) {
9724 if (objKey
.charAt(0) !== '$' && search(obj
[objKey
], text
)) {
9730 for ( var i
= 0; i
< obj
.length
; i
++) {
9731 if (search(obj
[i
], text
)) {
9740 switch (typeof expression
) {
9744 expression
= {$:expression
};
9746 for (var key
in expression
) {
9749 var text
= (''+expression
[key
]).toLowerCase();
9751 predicates
.push(function(value
) {
9752 return search(value
, text
);
9758 var text
= (''+expression
[key
]).toLowerCase();
9760 predicates
.push(function(value
) {
9761 return search(getter(value
, path
), text
);
9768 predicates
.push(expression
);
9774 for ( var j
= 0; j
< array
.length
; j
++) {
9775 var value
= array
[j
];
9776 if (predicates
.check(value
)) {
9777 filtered
.push(value
);
9786 * @name ng.filter:currency
9790 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
9791 * symbol for current locale is used.
9793 * @param {number} amount Input to filter.
9794 * @param {string=} symbol Currency symbol or identifier to be displayed.
9795 * @returns {string} Formatted number.
9802 function Ctrl($scope) {
9803 $scope.amount = 1234.56;
9806 <div ng-controller="Ctrl">
9807 <input type="number" ng-model="amount"> <br>
9808 default currency symbol ($): {{amount | currency}}<br>
9809 custom currency identifier (USD$): {{amount | currency:"USD$"}}
9813 it('should init with 1234.56', function() {
9814 expect(binding('amount | currency')).toBe('$1,234.56');
9815 expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
9817 it('should update', function() {
9818 input('amount').enter('-1234');
9819 expect(binding('amount | currency')).toBe('($1,234.00)');
9820 expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
9825 currencyFilter
.$inject
= ['$locale'];
9826 function currencyFilter($locale
) {
9827 var formats
= $locale
.NUMBER_FORMATS
;
9828 return function(amount
, currencySymbol
){
9829 if (isUndefined(currencySymbol
)) currencySymbol
= formats
.CURRENCY_SYM
;
9830 return formatNumber(amount
, formats
.PATTERNS
[1], formats
.GROUP_SEP
, formats
.DECIMAL_SEP
, 2).
9831 replace(/\u00A4/g, currencySymbol
);
9837 * @name ng.filter:number
9841 * Formats a number as text.
9843 * If the input is not a number an empty string is returned.
9845 * @param {number|string} number Number to format.
9846 * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
9847 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
9853 function Ctrl($scope) {
9854 $scope.val = 1234.56789;
9857 <div ng-controller="Ctrl">
9858 Enter number: <input ng-model='val'><br>
9859 Default formatting: {{val | number}}<br>
9860 No fractions: {{val | number:0}}<br>
9861 Negative number: {{-val | number:4}}
9865 it('should format numbers', function() {
9866 expect(binding('val | number')).toBe('1,234.568');
9867 expect(binding('val | number:0')).toBe('1,235');
9868 expect(binding('-val | number:4')).toBe('-1,234.5679');
9871 it('should update', function() {
9872 input('val').enter('3374.333');
9873 expect(binding('val | number')).toBe('3,374.333');
9874 expect(binding('val | number:0')).toBe('3,374');
9875 expect(binding('-val | number:4')).toBe('-3,374.3330');
9882 numberFilter
.$inject
= ['$locale'];
9883 function numberFilter($locale
) {
9884 var formats
= $locale
.NUMBER_FORMATS
;
9885 return function(number
, fractionSize
) {
9886 return formatNumber(number
, formats
.PATTERNS
[0], formats
.GROUP_SEP
, formats
.DECIMAL_SEP
,
9891 var DECIMAL_SEP
= '.';
9892 function formatNumber(number
, pattern
, groupSep
, decimalSep
, fractionSize
) {
9893 if (isNaN(number
) || !isFinite(number
)) return '';
9895 var isNegative
= number
< 0;
9896 number
= Math
.abs(number
);
9897 var numStr
= number
+ '',
9901 var hasExponent
= false;
9902 if (numStr
.indexOf('e') !== -1) {
9903 var match
= numStr
.match(/([\d\.]+)e(-?)(\d+)/);
9904 if (match
&& match
[2] == '-' && match
[3] > fractionSize
+ 1) {
9907 formatedText
= numStr
;
9913 var fractionLen
= (numStr
.split(DECIMAL_SEP
)[1] || '').length
;
9915 // determine fractionSize if it is not specified
9916 if (isUndefined(fractionSize
)) {
9917 fractionSize
= Math
.min(Math
.max(pattern
.minFrac
, fractionLen
), pattern
.maxFrac
);
9920 var pow
= Math
.pow(10, fractionSize
);
9921 number
= Math
.round(number
* pow
) / pow
;
9922 var fraction
= ('' + number
).split(DECIMAL_SEP
);
9923 var whole
= fraction
[0];
9924 fraction
= fraction
[1] || '';
9927 lgroup
= pattern
.lgSize
,
9928 group
= pattern
.gSize
;
9930 if (whole
.length
>= (lgroup
+ group
)) {
9931 pos
= whole
.length
- lgroup
;
9932 for (var i
= 0; i
< pos
; i
++) {
9933 if ((pos
- i
)%group
=== 0 && i
!== 0) {
9934 formatedText
+= groupSep
;
9936 formatedText
+= whole
.charAt(i
);
9940 for (i
= pos
; i
< whole
.length
; i
++) {
9941 if ((whole
.length
- i
)%lgroup
=== 0 && i
!== 0) {
9942 formatedText
+= groupSep
;
9944 formatedText
+= whole
.charAt(i
);
9947 // format fraction part.
9948 while(fraction
.length
< fractionSize
) {
9952 if (fractionSize
&& fractionSize
!== "0") formatedText
+= decimalSep
+ fraction
.substr(0, fractionSize
);
9955 parts
.push(isNegative
? pattern
.negPre : pattern
.posPre
);
9956 parts
.push(formatedText
);
9957 parts
.push(isNegative
? pattern
.negSuf : pattern
.posSuf
);
9958 return parts
.join('');
9961 function padNumber(num
, digits
, trim
) {
9968 while(num
.length
< digits
) num
= '0' + num
;
9970 num
= num
.substr(num
.length
- digits
);
9975 function dateGetter(name
, size
, offset
, trim
) {
9976 offset
= offset
|| 0;
9977 return function(date
) {
9978 var value
= date
['get' + name
]();
9979 if (offset
> 0 || value
> -offset
)
9981 if (value
=== 0 && offset
== -12 ) value
= 12;
9982 return padNumber(value
, size
, trim
);
9986 function dateStrGetter(name
, shortForm
) {
9987 return function(date
, formats
) {
9988 var value
= date
['get' + name
]();
9989 var get = uppercase(shortForm
? ('SHORT' + name
) : name
);
9991 return formats
[get][value
];
9995 function timeZoneGetter(date
) {
9996 var zone
= -1 * date
.getTimezoneOffset();
9997 var paddedZone
= (zone
>= 0) ? "+" : "";
9999 paddedZone
+= padNumber(Math
[zone
> 0 ? 'floor' : 'ceil'](zone
/ 60), 2) +
10000 padNumber(Math
.abs(zone
% 60), 2);
10005 function ampmGetter(date
, formats
) {
10006 return date
.getHours() < 12 ? formats
.AMPMS
[0] : formats
.AMPMS
[1];
10009 var DATE_FORMATS
= {
10010 yyyy: dateGetter('FullYear', 4),
10011 yy: dateGetter('FullYear', 2, 0, true),
10012 y: dateGetter('FullYear', 1),
10013 MMMM: dateStrGetter('Month'),
10014 MMM: dateStrGetter('Month', true),
10015 MM: dateGetter('Month', 2, 1),
10016 M: dateGetter('Month', 1, 1),
10017 dd: dateGetter('Date', 2),
10018 d: dateGetter('Date', 1),
10019 HH: dateGetter('Hours', 2),
10020 H: dateGetter('Hours', 1),
10021 hh: dateGetter('Hours', 2, -12),
10022 h: dateGetter('Hours', 1, -12),
10023 mm: dateGetter('Minutes', 2),
10024 m: dateGetter('Minutes', 1),
10025 ss: dateGetter('Seconds', 2),
10026 s: dateGetter('Seconds', 1),
10027 EEEE: dateStrGetter('Day'),
10028 EEE: dateStrGetter('Day', true),
10033 var DATE_FORMATS_SPLIT
= /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
10034 NUMBER_STRING
= /^\d+$/;
10038 * @name ng.filter:date
10042 * Formats `date` to a string based on the requested `format`.
10044 * `format` string can be composed of the following elements:
10046 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
10047 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
10048 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
10049 * * `'MMMM'`: Month in year (January-December)
10050 * * `'MMM'`: Month in year (Jan-Dec)
10051 * * `'MM'`: Month in year, padded (01-12)
10052 * * `'M'`: Month in year (1-12)
10053 * * `'dd'`: Day in month, padded (01-31)
10054 * * `'d'`: Day in month (1-31)
10055 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
10056 * * `'EEE'`: Day in Week, (Sun-Sat)
10057 * * `'HH'`: Hour in day, padded (00-23)
10058 * * `'H'`: Hour in day (0-23)
10059 * * `'hh'`: Hour in am/pm, padded (01-12)
10060 * * `'h'`: Hour in am/pm, (1-12)
10061 * * `'mm'`: Minute in hour, padded (00-59)
10062 * * `'m'`: Minute in hour (0-59)
10063 * * `'ss'`: Second in minute, padded (00-59)
10064 * * `'s'`: Second in minute (0-59)
10065 * * `'a'`: am/pm marker
10066 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
10068 * `format` string can also be one of the following predefined
10069 * {@link guide/i18n localizable formats}:
10071 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
10072 * (e.g. Sep 3, 2010 12:05:08 pm)
10073 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
10074 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
10075 * (e.g. Friday, September 3, 2010)
10076 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
10077 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
10078 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
10079 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
10080 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
10082 * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
10083 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
10084 * (e.g. `"h o''clock"`).
10086 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
10087 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its
10088 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
10089 * specified in the string input, the time is considered to be in the local timezone.
10090 * @param {string=} format Formatting rules (see Description). If not specified,
10091 * `mediumDate` is used.
10092 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
10097 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
10098 {{1288323623006 | date:'medium'}}<br>
10099 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
10100 {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
10101 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
10102 {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
10105 it('should format date', function() {
10106 expect(binding("1288323623006 | date:'medium'")).
10107 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
10108 expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
10109 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
10110 expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
10111 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
10116 dateFilter
.$inject
= ['$locale'];
10117 function dateFilter($locale
) {
10120 var R_ISO8601_STR
= /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
10121 function jsonStringToDate(string
){
10123 if (match
= string
.match(R_ISO8601_STR
)) {
10124 var date
= new Date(0),
10128 tzHour
= int(match
[9] + match
[10]);
10129 tzMin
= int(match
[9] + match
[11]);
10131 date
.setUTCFullYear(int(match
[1]), int(match
[2]) - 1, int(match
[3]));
10132 date
.setUTCHours(int(match
[4]||0) - tzHour
, int(match
[5]||0) - tzMin
, int(match
[6]||0), int(match
[7]||0));
10139 return function(date
, format
) {
10144 format
= format
|| 'mediumDate';
10145 format
= $locale
.DATETIME_FORMATS
[format
] || format
;
10146 if (isString(date
)) {
10147 if (NUMBER_STRING
.test(date
)) {
10150 date
= jsonStringToDate(date
);
10154 if (isNumber(date
)) {
10155 date
= new Date(date
);
10158 if (!isDate(date
)) {
10163 match
= DATE_FORMATS_SPLIT
.exec(format
);
10165 parts
= concat(parts
, match
, 1);
10166 format
= parts
.pop();
10168 parts
.push(format
);
10173 forEach(parts
, function(value
){
10174 fn
= DATE_FORMATS
[value
];
10175 text
+= fn
? fn(date
, $locale
.DATETIME_FORMATS
)
10176 : value
.replace(/(^'|'$)/g, '').replace(/''/g, "'");
10186 * @name ng.filter:json
10190 * Allows you to convert a JavaScript object into JSON string.
10192 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
10193 * the binding is automatically converted to JSON.
10195 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
10196 * @returns {string} JSON string.
10202 <pre>{{ {'name':'value'} | json }}</pre>
10205 it('should jsonify filtered objects', function() {
10206 expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
10212 function jsonFilter() {
10213 return function(object
) {
10214 return toJson(object
, true);
10221 * @name ng.filter:lowercase
10224 * Converts string to lowercase.
10225 * @see angular.lowercase
10227 var lowercaseFilter
= valueFn(lowercase
);
10232 * @name ng.filter:uppercase
10235 * Converts string to uppercase.
10236 * @see angular.uppercase
10238 var uppercaseFilter
= valueFn(uppercase
);
10242 * @name ng.filter:limitTo
10246 * Creates a new array containing only a specified number of elements in an array. The elements
10247 * are taken from either the beginning or the end of the source array, as specified by the
10248 * value and sign (positive or negative) of `limit`.
10250 * Note: This function is used to augment the `Array` type in Angular expressions. See
10251 * {@link ng.$filter} for more information about Angular arrays.
10253 * @param {Array} array Source array to be limited.
10254 * @param {string|Number} limit The length of the returned array. If the `limit` number is
10255 * positive, `limit` number of items from the beginning of the source array are copied.
10256 * If the number is negative, `limit` number of items from the end of the source array are
10257 * copied. The `limit` will be trimmed if it exceeds `array.length`
10258 * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
10265 function Ctrl($scope) {
10266 $scope.numbers = [1,2,3,4,5,6,7,8,9];
10270 <div ng-controller="Ctrl">
10271 Limit {{numbers}} to: <input type="integer" ng-model="limit">
10272 <p>Output: {{ numbers | limitTo:limit }}</p>
10276 it('should limit the numer array to first three items', function() {
10277 expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
10278 expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
10281 it('should update the output when -3 is entered', function() {
10282 input('limit').enter(-3);
10283 expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
10286 it('should not exceed the maximum size of input array', function() {
10287 input('limit').enter(100);
10288 expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
10293 function limitToFilter(){
10294 return function(array
, limit
) {
10295 if (!(array
instanceof Array
)) return array
;
10296 limit
= int(limit
);
10300 // check that array is iterable
10301 if (!array
|| !(array
instanceof Array
))
10304 // if abs(limit) exceeds maximum length, trim it
10305 if (limit
> array
.length
)
10306 limit
= array
.length
;
10307 else if (limit
< -array
.length
)
10308 limit
= -array
.length
;
10314 i
= array
.length
+ limit
;
10319 out
.push(array
[i
]);
10328 * @name ng.filter:orderBy
10332 * Orders a specified `array` by the `expression` predicate.
10334 * Note: this function is used to augment the `Array` type in Angular expressions. See
10335 * {@link ng.$filter} for more informaton about Angular arrays.
10337 * @param {Array} array The array to sort.
10338 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
10339 * used by the comparator to determine the order of elements.
10343 * - `function`: Getter function. The result of this function will be sorted using the
10344 * `<`, `=`, `>` operator.
10345 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
10346 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
10347 * ascending or descending sort order (for example, +name or -name).
10348 * - `Array`: An array of function or string predicates. The first predicate in the array
10349 * is used for sorting, but when two items are equivalent, the next predicate is used.
10351 * @param {boolean=} reverse Reverse the order the array.
10352 * @returns {Array} Sorted copy of the source array.
10358 function Ctrl($scope) {
10360 [{name:'John', phone:'555-1212', age:10},
10361 {name:'Mary', phone:'555-9876', age:19},
10362 {name:'Mike', phone:'555-4321', age:21},
10363 {name:'Adam', phone:'555-5678', age:35},
10364 {name:'Julie', phone:'555-8765', age:29}]
10365 $scope.predicate = '-age';
10368 <div ng-controller="Ctrl">
10369 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
10371 [ <a href="" ng-click="predicate=''">unsorted</a> ]
10372 <table class="friend">
10374 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a>
10375 (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th>
10376 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
10377 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
10379 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
10380 <td>{{friend.name}}</td>
10381 <td>{{friend.phone}}</td>
10382 <td>{{friend.age}}</td>
10388 it('should be reverse ordered by aged', function() {
10389 expect(binding('predicate')).toBe('-age');
10390 expect(repeater('table.friend', 'friend in friends').column('friend.age')).
10391 toEqual(['35', '29', '21', '19', '10']);
10392 expect(repeater('table.friend', 'friend in friends').column('friend.name')).
10393 toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
10396 it('should reorder the table when user selects different predicate', function() {
10397 element('.doc-example-live a:contains("Name")').click();
10398 expect(repeater('table.friend', 'friend in friends').column('friend.name')).
10399 toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
10400 expect(repeater('table.friend', 'friend in friends').column('friend.age')).
10401 toEqual(['35', '10', '29', '19', '21']);
10403 element('.doc-example-live a:contains("Phone")').click();
10404 expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
10405 toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
10406 expect(repeater('table.friend', 'friend in friends').column('friend.name')).
10407 toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
10412 orderByFilter
.$inject
= ['$parse'];
10413 function orderByFilter($parse
){
10414 return function(array
, sortPredicate
, reverseOrder
) {
10415 if (!isArray(array
)) return array
;
10416 if (!sortPredicate
) return array
;
10417 sortPredicate
= isArray(sortPredicate
) ? sortPredicate: [sortPredicate
];
10418 sortPredicate
= map(sortPredicate
, function(predicate
){
10419 var descending
= false, get = predicate
|| identity
;
10420 if (isString(predicate
)) {
10421 if ((predicate
.charAt(0) == '+' || predicate
.charAt(0) == '-')) {
10422 descending
= predicate
.charAt(0) == '-';
10423 predicate
= predicate
.substring(1);
10425 get = $parse(predicate
);
10427 return reverseComparator(function(a
,b
){
10428 return compare(get(a
),get(b
));
10431 var arrayCopy
= [];
10432 for ( var i
= 0; i
< array
.length
; i
++) { arrayCopy
.push(array
[i
]); }
10433 return arrayCopy
.sort(reverseComparator(comparator
, reverseOrder
));
10435 function comparator(o1
, o2
){
10436 for ( var i
= 0; i
< sortPredicate
.length
; i
++) {
10437 var comp
= sortPredicate
[i
](o1
, o2
);
10438 if (comp
!== 0) return comp
;
10442 function reverseComparator(comp
, descending
) {
10443 return toBoolean(descending
)
10444 ? function(a
,b
){return comp(b
,a
);}
10447 function compare(v1
, v2
){
10448 var t1
= typeof v1
;
10449 var t2
= typeof v2
;
10451 if (t1
== "string") v1
= v1
.toLowerCase();
10452 if (t1
== "string") v2
= v2
.toLowerCase();
10453 if (v1
=== v2
) return 0;
10454 return v1
< v2
? -1 : 1;
10456 return t1
< t2
? -1 : 1;
10462 function ngDirective(directive
) {
10463 if (isFunction(directive
)) {
10468 directive
.restrict
= directive
.restrict
|| 'AC';
10469 return valueFn(directive
);
10474 * @name ng.directive:a
10478 * Modifies the default behavior of html A tag, so that the default action is prevented when href
10479 * attribute is empty.
10481 * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
10482 * without changing the location or causing page reloads, e.g.:
10483 * `<a href="" ng-click="model.$save()">Save</a>`
10485 var htmlAnchorDirective
= valueFn({
10487 compile: function(element
, attr
) {
10491 // turn <a href ng-click="..">link</a> into a stylable link in IE
10492 // but only if it doesn't have name attribute, in which case it's an anchor
10493 if (!attr
.href
&& !attr
.name
) {
10494 attr
.$set('href', '');
10497 // add a comment node to anchors to workaround IE bug that causes element content to be reset
10498 // to new attribute content if attribute is updated with value containing @ and element also
10499 // contains value with @
10501 element
.append(document
.createComment('IE fix'));
10504 return function(scope
, element
) {
10505 element
.bind('click', function(event
){
10506 // if we have no href url, then don't navigate anywhere.
10507 if (!element
.attr('href')) {
10508 event
.preventDefault();
10517 * @name ng.directive:ngHref
10521 * Using Angular markup like {{hash}} in an href attribute makes
10522 * the page open to a wrong URL, if the user clicks that link before
10523 * angular has a chance to replace the {{hash}} with actual URL, the
10524 * link will be broken and will most likely return a 404 error.
10525 * The `ngHref` directive solves this problem.
10527 * The buggy way to write it:
10529 * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
10532 * The correct way to write it:
10534 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
10538 * @param {template} ngHref any string which can contain `{{}}` markup.
10541 * This example uses `link` variable inside `href` attribute:
10544 <input ng-model="value" /><br />
10545 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
10546 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
10547 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
10548 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
10549 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
10550 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
10553 it('should execute ng-click but not reload when href without value', function() {
10554 element('#link-1').click();
10555 expect(input('value').val()).toEqual('1');
10556 expect(element('#link-1').attr('href')).toBe("");
10559 it('should execute ng-click but not reload when href empty string', function() {
10560 element('#link-2').click();
10561 expect(input('value').val()).toEqual('2');
10562 expect(element('#link-2').attr('href')).toBe("");
10565 it('should execute ng-click and change url when ng-href specified', function() {
10566 expect(element('#link-3').attr('href')).toBe("/123");
10568 element('#link-3').click();
10569 expect(browser().window().path()).toEqual('/123');
10572 it('should execute ng-click but not reload when href empty string and name specified', function() {
10573 element('#link-4').click();
10574 expect(input('value').val()).toEqual('4');
10575 expect(element('#link-4').attr('href')).toBe('');
10578 it('should execute ng-click but not reload when no href but name specified', function() {
10579 element('#link-5').click();
10580 expect(input('value').val()).toEqual('5');
10581 expect(element('#link-5').attr('href')).toBe(undefined);
10584 it('should only change url when only ng-href', function() {
10585 input('value').enter('6');
10586 expect(element('#link-6').attr('href')).toBe('6');
10588 element('#link-6').click();
10589 expect(browser().location().url()).toEqual('/6');
10597 * @name ng.directive:ngSrc
10601 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
10602 * work right: The browser will fetch from the URL with the literal
10603 * text `{{hash}}` until Angular replaces the expression inside
10604 * `{{hash}}`. The `ngSrc` directive solves this problem.
10606 * The buggy way to write it:
10608 * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
10611 * The correct way to write it:
10613 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
10617 * @param {template} ngSrc any string which can contain `{{}}` markup.
10622 * @name ng.directive:ngDisabled
10627 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
10629 * <div ng-init="scope = { isDisabled: false }">
10630 * <button disabled="{{scope.isDisabled}}">Disabled</button>
10634 * The HTML specs do not require browsers to preserve the special attributes such as disabled.
10635 * (The presence of them means true and absence means false)
10636 * This prevents the angular compiler from correctly retrieving the binding expression.
10637 * To solve this problem, we introduce the `ngDisabled` directive.
10642 Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
10643 <button ng-model="button" ng-disabled="checked">Button</button>
10646 it('should toggle button', function() {
10647 expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
10648 input('checked').check();
10649 expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
10655 * @param {expression} ngDisabled Angular expression that will be evaluated.
10661 * @name ng.directive:ngChecked
10665 * The HTML specs do not require browsers to preserve the special attributes such as checked.
10666 * (The presence of them means true and absence means false)
10667 * This prevents the angular compiler from correctly retrieving the binding expression.
10668 * To solve this problem, we introduce the `ngChecked` directive.
10672 Check me to check both: <input type="checkbox" ng-model="master"><br/>
10673 <input id="checkSlave" type="checkbox" ng-checked="master">
10676 it('should check both checkBoxes', function() {
10677 expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
10678 input('master').check();
10679 expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
10685 * @param {expression} ngChecked Angular expression that will be evaluated.
10691 * @name ng.directive:ngMultiple
10695 * The HTML specs do not require browsers to preserve the special attributes such as multiple.
10696 * (The presence of them means true and absence means false)
10697 * This prevents the angular compiler from correctly retrieving the binding expression.
10698 * To solve this problem, we introduce the `ngMultiple` directive.
10703 Check me check multiple: <input type="checkbox" ng-model="checked"><br/>
10704 <select id="select" ng-multiple="checked">
10705 <option>Misko</option>
10706 <option>Igor</option>
10707 <option>Vojta</option>
10708 <option>Di</option>
10712 it('should toggle multiple', function() {
10713 expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
10714 input('checked').check();
10715 expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
10721 * @param {expression} ngMultiple Angular expression that will be evaluated.
10727 * @name ng.directive:ngReadonly
10731 * The HTML specs do not require browsers to preserve the special attributes such as readonly.
10732 * (The presence of them means true and absence means false)
10733 * This prevents the angular compiler from correctly retrieving the binding expression.
10734 * To solve this problem, we introduce the `ngReadonly` directive.
10738 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
10739 <input type="text" ng-readonly="checked" value="I'm Angular"/>
10742 it('should toggle readonly attr', function() {
10743 expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
10744 input('checked').check();
10745 expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
10751 * @param {string} expression Angular expression that will be evaluated.
10757 * @name ng.directive:ngSelected
10761 * The HTML specs do not require browsers to preserve the special attributes such as selected.
10762 * (The presence of them means true and absence means false)
10763 * This prevents the angular compiler from correctly retrieving the binding expression.
10764 * To solve this problem, we introduced the `ngSelected` directive.
10768 Check me to select: <input type="checkbox" ng-model="selected"><br/>
10770 <option>Hello!</option>
10771 <option id="greet" ng-selected="selected">Greetings!</option>
10775 it('should select Greetings!', function() {
10776 expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
10777 input('selected').check();
10778 expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
10784 * @param {string} expression Angular expression that will be evaluated.
10788 var ngAttributeAliasDirectives
= {};
10791 // boolean attrs are evaluated
10792 forEach(BOOLEAN_ATTR
, function(propName
, attrName
) {
10793 var normalized
= directiveNormalize('ng-' + attrName
);
10794 ngAttributeAliasDirectives
[normalized
] = function() {
10797 compile: function() {
10798 return function(scope
, element
, attr
) {
10799 scope
.$watch(attr
[normalized
], function ngBooleanAttrWatchAction(value
) {
10800 attr
.$set(attrName
, !!value
);
10809 // ng-src, ng-href are interpolated
10810 forEach(['src', 'href'], function(attrName
) {
10811 var normalized
= directiveNormalize('ng-' + attrName
);
10812 ngAttributeAliasDirectives
[normalized
] = function() {
10814 priority: 99, // it needs to run after the attributes are interpolated
10815 link: function(scope
, element
, attr
) {
10816 attr
.$observe(normalized
, function(value
) {
10820 attr
.$set(attrName
, value
);
10822 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
10823 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
10824 // to set the property as well to achieve the desired effect.
10825 // we use attr[attrName] value since $set can sanitize the url.
10826 if (msie
) element
.prop(attrName
, attr
[attrName
]);
10833 var nullFormCtrl
= {
10835 $removeControl: noop
,
10836 $setValidity: noop
,
10842 * @name ng.directive:form.FormController
10844 * @property {boolean} $pristine True if user has not interacted with the form yet.
10845 * @property {boolean} $dirty True if user has already interacted with the form.
10846 * @property {boolean} $valid True if all of the containing forms and controls are valid.
10847 * @property {boolean} $invalid True if at least one containing control or form is invalid.
10849 * @property {Object} $error Is an object hash, containing references to all invalid controls or
10852 * - keys are validation tokens (error names) — such as `required`, `url` or `email`),
10853 * - values are arrays of controls or forms that are invalid with given error.
10856 * `FormController` keeps track of all its controls and nested forms as well as state of them,
10857 * such as being valid/invalid or dirty/pristine.
10859 * Each {@link ng.directive:form form} directive creates an instance
10860 * of `FormController`.
10863 //asks for $scope to fool the BC controller module
10864 FormController
.$inject
= ['$element', '$attrs', '$scope'];
10865 function FormController(element
, attrs
) {
10867 parentForm
= element
.parent().controller('form') || nullFormCtrl
,
10868 invalidCount
= 0, // used to easily determine if we are valid
10869 errors
= form
.$error
= {};
10872 form
.$name
= attrs
.name
;
10873 form
.$dirty
= false;
10874 form
.$pristine
= true;
10875 form
.$valid
= true;
10876 form
.$invalid
= false;
10878 parentForm
.$addControl(form
);
10880 // Setup initial state of the control
10881 element
.addClass(PRISTINE_CLASS
);
10882 toggleValidCss(true);
10884 // convenience method for easy toggling of classes
10885 function toggleValidCss(isValid
, validationErrorKey
) {
10886 validationErrorKey
= validationErrorKey
? '-' + snake_case(validationErrorKey
, '-') : '';
10888 removeClass((isValid
? INVALID_CLASS : VALID_CLASS
) + validationErrorKey
).
10889 addClass((isValid
? VALID_CLASS : INVALID_CLASS
) + validationErrorKey
);
10892 form
.$addControl = function(control
) {
10893 if (control
.$name
&& !form
.hasOwnProperty(control
.$name
)) {
10894 form
[control
.$name
] = control
;
10898 form
.$removeControl = function(control
) {
10899 if (control
.$name
&& form
[control
.$name
] === control
) {
10900 delete form
[control
.$name
];
10902 forEach(errors
, function(queue
, validationToken
) {
10903 form
.$setValidity(validationToken
, true, control
);
10907 form
.$setValidity = function(validationToken
, isValid
, control
) {
10908 var queue
= errors
[validationToken
];
10912 arrayRemove(queue
, control
);
10913 if (!queue
.length
) {
10915 if (!invalidCount
) {
10916 toggleValidCss(isValid
);
10917 form
.$valid
= true;
10918 form
.$invalid
= false;
10920 errors
[validationToken
] = false;
10921 toggleValidCss(true, validationToken
);
10922 parentForm
.$setValidity(validationToken
, true, form
);
10927 if (!invalidCount
) {
10928 toggleValidCss(isValid
);
10931 if (includes(queue
, control
)) return;
10933 errors
[validationToken
] = queue
= [];
10935 toggleValidCss(false, validationToken
);
10936 parentForm
.$setValidity(validationToken
, false, form
);
10938 queue
.push(control
);
10940 form
.$valid
= false;
10941 form
.$invalid
= true;
10945 form
.$setDirty = function() {
10946 element
.removeClass(PRISTINE_CLASS
).addClass(DIRTY_CLASS
);
10947 form
.$dirty
= true;
10948 form
.$pristine
= false;
10949 parentForm
.$setDirty();
10957 * @name ng.directive:ngForm
10961 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
10962 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
10963 * sub-group of controls needs to be determined.
10965 * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into
10966 * related scope, under this name.
10972 * @name ng.directive:form
10976 * Directive that instantiates
10977 * {@link ng.directive:form.FormController FormController}.
10979 * If `name` attribute is specified, the form controller is published onto the current scope under
10982 * # Alias: {@link ng.directive:ngForm `ngForm`}
10984 * In angular forms can be nested. This means that the outer form is valid when all of the child
10985 * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
10986 * reason angular provides {@link ng.directive:ngForm `ngForm`} alias
10987 * which behaves identical to `<form>` but allows form nesting.
10991 * - `ng-valid` Is set if the form is valid.
10992 * - `ng-invalid` Is set if the form is invalid.
10993 * - `ng-pristine` Is set if the form is pristine.
10994 * - `ng-dirty` Is set if the form is dirty.
10997 * # Submitting a form and preventing default action
10999 * Since the role of forms in client-side Angular applications is different than in classical
11000 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
11001 * page reload that sends the data to the server. Instead some javascript logic should be triggered
11002 * to handle the form submission in application specific way.
11004 * For this reason, Angular prevents the default action (form submission to the server) unless the
11005 * `<form>` element has an `action` attribute specified.
11007 * You can use one of the following two ways to specify what javascript method should be called when
11008 * a form is submitted:
11010 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
11011 * - {@link ng.directive:ngClick ngClick} directive on the first
11012 * button or input field of type submit (input[type=submit])
11014 * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
11015 * is because of the following form submission rules coming from the html spec:
11017 * - If a form has only one input field then hitting enter in this field triggers form submit
11019 * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
11020 * doesn't trigger submit
11021 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
11022 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
11023 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
11025 * @param {string=} name Name of the form. If specified, the form controller will be published into
11026 * related scope, under this name.
11032 function Ctrl($scope) {
11033 $scope.userType = 'guest';
11036 <form name="myForm" ng-controller="Ctrl">
11037 userType: <input name="input" ng-model="userType" required>
11038 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
11039 <tt>userType = {{userType}}</tt><br>
11040 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
11041 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
11042 <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
11043 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
11047 it('should initialize to model', function() {
11048 expect(binding('userType')).toEqual('guest');
11049 expect(binding('myForm.input.$valid')).toEqual('true');
11052 it('should be invalid if empty', function() {
11053 input('userType').enter('');
11054 expect(binding('userType')).toEqual('');
11055 expect(binding('myForm.input.$valid')).toEqual('false');
11060 var formDirectiveFactory = function(isNgForm
) {
11061 return ['$timeout', function($timeout
) {
11062 var formDirective
= {
11065 controller: FormController
,
11066 compile: function() {
11068 pre: function(scope
, formElement
, attr
, controller
) {
11069 if (!attr
.action
) {
11070 // we can't use jq events because if a form is destroyed during submission the default
11071 // action is not prevented. see #1238
11073 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
11074 // page reload if the form was destroyed by submission of the form via a click handler
11075 // on a button in the form. Looks like an IE9 specific bug.
11076 var preventDefaultListener = function(event
) {
11077 event
.preventDefault
11078 ? event
.preventDefault()
11079 : event
.returnValue
= false; // IE
11082 addEventListenerFn(formElement
[0], 'submit', preventDefaultListener
);
11084 // unregister the preventDefault listener so that we don't not leak memory but in a
11085 // way that will achieve the prevention of the default action.
11086 formElement
.bind('$destroy', function() {
11087 $timeout(function() {
11088 removeEventListenerFn(formElement
[0], 'submit', preventDefaultListener
);
11093 var parentFormCtrl
= formElement
.parent().controller('form'),
11094 alias
= attr
.name
|| attr
.ngForm
;
11097 scope
[alias
] = controller
;
11099 if (parentFormCtrl
) {
11100 formElement
.bind('$destroy', function() {
11101 parentFormCtrl
.$removeControl(controller
);
11103 scope
[alias
] = undefined;
11105 extend(controller
, nullFormCtrl
); //stop propagating child destruction handlers upwards
11113 return isNgForm
? extend(copy(formDirective
), {restrict: 'EAC'}) : formDirective
;
11117 var formDirective
= formDirectiveFactory();
11118 var ngFormDirective
= formDirectiveFactory(true);
11120 var URL_REGEXP
= /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
11121 var EMAIL_REGEXP
= /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
11122 var NUMBER_REGEXP
= /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
11128 * @name ng.directive:input.text
11131 * Standard HTML text input with angular data binding.
11133 * @param {string} ngModel Assignable angular expression to data-bind to.
11134 * @param {string=} name Property name of the form under which the control is published.
11135 * @param {string=} required Adds `required` validation error key if the value is not entered.
11136 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11137 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11138 * `required` when you want to data-bind to the `required` attribute.
11139 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11141 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11143 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
11144 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
11145 * patterns defined as scope expressions.
11146 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11147 * interaction with the input element.
11153 function Ctrl($scope) {
11154 $scope.text = 'guest';
11155 $scope.word = /^\w*$/;
11158 <form name="myForm" ng-controller="Ctrl">
11159 Single word: <input type="text" name="input" ng-model="text"
11160 ng-pattern="word" required>
11161 <span class="error" ng-show="myForm.input.$error.required">
11163 <span class="error" ng-show="myForm.input.$error.pattern">
11164 Single word only!</span>
11166 <tt>text = {{text}}</tt><br/>
11167 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
11168 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
11169 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
11170 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
11174 it('should initialize to model', function() {
11175 expect(binding('text')).toEqual('guest');
11176 expect(binding('myForm.input.$valid')).toEqual('true');
11179 it('should be invalid if empty', function() {
11180 input('text').enter('');
11181 expect(binding('text')).toEqual('');
11182 expect(binding('myForm.input.$valid')).toEqual('false');
11185 it('should be invalid if multi word', function() {
11186 input('text').enter('hello world');
11187 expect(binding('myForm.input.$valid')).toEqual('false');
11192 'text': textInputType
,
11197 * @name ng.directive:input.number
11200 * Text input with number validation and transformation. Sets the `number` validation
11201 * error if not a valid number.
11203 * @param {string} ngModel Assignable angular expression to data-bind to.
11204 * @param {string=} name Property name of the form under which the control is published.
11205 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
11206 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
11207 * @param {string=} required Sets `required` validation error key if the value is not entered.
11208 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11209 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11210 * `required` when you want to data-bind to the `required` attribute.
11211 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11213 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11215 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
11216 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
11217 * patterns defined as scope expressions.
11218 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11219 * interaction with the input element.
11225 function Ctrl($scope) {
11229 <form name="myForm" ng-controller="Ctrl">
11230 Number: <input type="number" name="input" ng-model="value"
11231 min="0" max="99" required>
11232 <span class="error" ng-show="myForm.list.$error.required">
11234 <span class="error" ng-show="myForm.list.$error.number">
11235 Not valid number!</span>
11236 <tt>value = {{value}}</tt><br/>
11237 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
11238 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
11239 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
11240 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
11244 it('should initialize to model', function() {
11245 expect(binding('value')).toEqual('12');
11246 expect(binding('myForm.input.$valid')).toEqual('true');
11249 it('should be invalid if empty', function() {
11250 input('value').enter('');
11251 expect(binding('value')).toEqual('');
11252 expect(binding('myForm.input.$valid')).toEqual('false');
11255 it('should be invalid if over max', function() {
11256 input('value').enter('123');
11257 expect(binding('value')).toEqual('');
11258 expect(binding('myForm.input.$valid')).toEqual('false');
11263 'number': numberInputType
,
11268 * @name ng.directive:input.url
11271 * Text input with URL validation. Sets the `url` validation error key if the content is not a
11274 * @param {string} ngModel Assignable angular expression to data-bind to.
11275 * @param {string=} name Property name of the form under which the control is published.
11276 * @param {string=} required Sets `required` validation error key if the value is not entered.
11277 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11278 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11279 * `required` when you want to data-bind to the `required` attribute.
11280 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11282 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11284 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
11285 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
11286 * patterns defined as scope expressions.
11287 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11288 * interaction with the input element.
11294 function Ctrl($scope) {
11295 $scope.text = 'http://google.com';
11298 <form name="myForm" ng-controller="Ctrl">
11299 URL: <input type="url" name="input" ng-model="text" required>
11300 <span class="error" ng-show="myForm.input.$error.required">
11302 <span class="error" ng-show="myForm.input.$error.url">
11303 Not valid url!</span>
11304 <tt>text = {{text}}</tt><br/>
11305 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
11306 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
11307 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
11308 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
11309 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
11313 it('should initialize to model', function() {
11314 expect(binding('text')).toEqual('http://google.com');
11315 expect(binding('myForm.input.$valid')).toEqual('true');
11318 it('should be invalid if empty', function() {
11319 input('text').enter('');
11320 expect(binding('text')).toEqual('');
11321 expect(binding('myForm.input.$valid')).toEqual('false');
11324 it('should be invalid if not url', function() {
11325 input('text').enter('xxx');
11326 expect(binding('myForm.input.$valid')).toEqual('false');
11331 'url': urlInputType
,
11336 * @name ng.directive:input.email
11339 * Text input with email validation. Sets the `email` validation error key if not a valid email
11342 * @param {string} ngModel Assignable angular expression to data-bind to.
11343 * @param {string=} name Property name of the form under which the control is published.
11344 * @param {string=} required Sets `required` validation error key if the value is not entered.
11345 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11346 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11347 * `required` when you want to data-bind to the `required` attribute.
11348 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11350 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11352 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
11353 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
11354 * patterns defined as scope expressions.
11360 function Ctrl($scope) {
11361 $scope.text = 'me@example.com';
11364 <form name="myForm" ng-controller="Ctrl">
11365 Email: <input type="email" name="input" ng-model="text" required>
11366 <span class="error" ng-show="myForm.input.$error.required">
11368 <span class="error" ng-show="myForm.input.$error.email">
11369 Not valid email!</span>
11370 <tt>text = {{text}}</tt><br/>
11371 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
11372 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
11373 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
11374 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
11375 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
11379 it('should initialize to model', function() {
11380 expect(binding('text')).toEqual('me@example.com');
11381 expect(binding('myForm.input.$valid')).toEqual('true');
11384 it('should be invalid if empty', function() {
11385 input('text').enter('');
11386 expect(binding('text')).toEqual('');
11387 expect(binding('myForm.input.$valid')).toEqual('false');
11390 it('should be invalid if not email', function() {
11391 input('text').enter('xxx');
11392 expect(binding('myForm.input.$valid')).toEqual('false');
11397 'email': emailInputType
,
11402 * @name ng.directive:input.radio
11405 * HTML radio button.
11407 * @param {string} ngModel Assignable angular expression to data-bind to.
11408 * @param {string} value The value to which the expression should be set when selected.
11409 * @param {string=} name Property name of the form under which the control is published.
11410 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11411 * interaction with the input element.
11417 function Ctrl($scope) {
11418 $scope.color = 'blue';
11421 <form name="myForm" ng-controller="Ctrl">
11422 <input type="radio" ng-model="color" value="red"> Red <br/>
11423 <input type="radio" ng-model="color" value="green"> Green <br/>
11424 <input type="radio" ng-model="color" value="blue"> Blue <br/>
11425 <tt>color = {{color}}</tt><br/>
11429 it('should change state', function() {
11430 expect(binding('color')).toEqual('blue');
11432 input('color').select('red');
11433 expect(binding('color')).toEqual('red');
11438 'radio': radioInputType
,
11443 * @name ng.directive:input.checkbox
11448 * @param {string} ngModel Assignable angular expression to data-bind to.
11449 * @param {string=} name Property name of the form under which the control is published.
11450 * @param {string=} ngTrueValue The value to which the expression should be set when selected.
11451 * @param {string=} ngFalseValue The value to which the expression should be set when not selected.
11452 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11453 * interaction with the input element.
11459 function Ctrl($scope) {
11460 $scope.value1 = true;
11461 $scope.value2 = 'YES'
11464 <form name="myForm" ng-controller="Ctrl">
11465 Value1: <input type="checkbox" ng-model="value1"> <br/>
11466 Value2: <input type="checkbox" ng-model="value2"
11467 ng-true-value="YES" ng-false-value="NO"> <br/>
11468 <tt>value1 = {{value1}}</tt><br/>
11469 <tt>value2 = {{value2}}</tt><br/>
11473 it('should change state', function() {
11474 expect(binding('value1')).toEqual('true');
11475 expect(binding('value2')).toEqual('YES');
11477 input('value1').check();
11478 input('value2').check();
11479 expect(binding('value1')).toEqual('false');
11480 expect(binding('value2')).toEqual('NO');
11485 'checkbox': checkboxInputType
,
11494 function isEmpty(value
) {
11495 return isUndefined(value
) || value
=== '' || value
=== null || value
!== value
;
11499 function textInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
) {
11501 var listener = function() {
11502 var value
= trim(element
.val());
11504 if (ctrl
.$viewValue
!== value
) {
11505 scope
.$apply(function() {
11506 ctrl
.$setViewValue(value
);
11511 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
11512 // input event on backspace, delete or cut
11513 if ($sniffer
.hasEvent('input')) {
11514 element
.bind('input', listener
);
11518 var deferListener = function() {
11520 timeout
= $browser
.defer(function() {
11527 element
.bind('keydown', function(event
) {
11528 var key
= event
.keyCode
;
11531 // command modifiers arrows
11532 if (key
=== 91 || (15 < key
&& key
< 19) || (37 <= key
&& key
<= 40)) return;
11537 // if user paste into input using mouse, we need "change" event to catch it
11538 element
.bind('change', listener
);
11540 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
11541 if ($sniffer
.hasEvent('paste')) {
11542 element
.bind('paste cut', deferListener
);
11547 ctrl
.$render = function() {
11548 element
.val(isEmpty(ctrl
.$viewValue
) ? '' : ctrl
.$viewValue
);
11551 // pattern validator
11552 var pattern
= attr
.ngPattern
,
11555 var validate = function(regexp
, value
) {
11556 if (isEmpty(value
) || regexp
.test(value
)) {
11557 ctrl
.$setValidity('pattern', true);
11560 ctrl
.$setValidity('pattern', false);
11566 if (pattern
.match(/^\/(.*)\/$/)) {
11567 pattern
= new RegExp(pattern
.substr(1, pattern
.length
- 2));
11568 patternValidator = function(value
) {
11569 return validate(pattern
, value
)
11572 patternValidator = function(value
) {
11573 var patternObj
= scope
.$eval(pattern
);
11575 if (!patternObj
|| !patternObj
.test
) {
11576 throw new Error('Expected ' + pattern
+ ' to be a RegExp but was ' + patternObj
);
11578 return validate(patternObj
, value
);
11582 ctrl
.$formatters
.push(patternValidator
);
11583 ctrl
.$parsers
.push(patternValidator
);
11586 // min length validator
11587 if (attr
.ngMinlength
) {
11588 var minlength
= int(attr
.ngMinlength
);
11589 var minLengthValidator = function(value
) {
11590 if (!isEmpty(value
) && value
.length
< minlength
) {
11591 ctrl
.$setValidity('minlength', false);
11594 ctrl
.$setValidity('minlength', true);
11599 ctrl
.$parsers
.push(minLengthValidator
);
11600 ctrl
.$formatters
.push(minLengthValidator
);
11603 // max length validator
11604 if (attr
.ngMaxlength
) {
11605 var maxlength
= int(attr
.ngMaxlength
);
11606 var maxLengthValidator = function(value
) {
11607 if (!isEmpty(value
) && value
.length
> maxlength
) {
11608 ctrl
.$setValidity('maxlength', false);
11611 ctrl
.$setValidity('maxlength', true);
11616 ctrl
.$parsers
.push(maxLengthValidator
);
11617 ctrl
.$formatters
.push(maxLengthValidator
);
11621 function numberInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
) {
11622 textInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
);
11624 ctrl
.$parsers
.push(function(value
) {
11625 var empty
= isEmpty(value
);
11626 if (empty
|| NUMBER_REGEXP
.test(value
)) {
11627 ctrl
.$setValidity('number', true);
11628 return value
=== '' ? null : (empty
? value : parseFloat(value
));
11630 ctrl
.$setValidity('number', false);
11635 ctrl
.$formatters
.push(function(value
) {
11636 return isEmpty(value
) ? '' : '' + value
;
11640 var min
= parseFloat(attr
.min
);
11641 var minValidator = function(value
) {
11642 if (!isEmpty(value
) && value
< min
) {
11643 ctrl
.$setValidity('min', false);
11646 ctrl
.$setValidity('min', true);
11651 ctrl
.$parsers
.push(minValidator
);
11652 ctrl
.$formatters
.push(minValidator
);
11656 var max
= parseFloat(attr
.max
);
11657 var maxValidator = function(value
) {
11658 if (!isEmpty(value
) && value
> max
) {
11659 ctrl
.$setValidity('max', false);
11662 ctrl
.$setValidity('max', true);
11667 ctrl
.$parsers
.push(maxValidator
);
11668 ctrl
.$formatters
.push(maxValidator
);
11671 ctrl
.$formatters
.push(function(value
) {
11673 if (isEmpty(value
) || isNumber(value
)) {
11674 ctrl
.$setValidity('number', true);
11677 ctrl
.$setValidity('number', false);
11683 function urlInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
) {
11684 textInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
);
11686 var urlValidator = function(value
) {
11687 if (isEmpty(value
) || URL_REGEXP
.test(value
)) {
11688 ctrl
.$setValidity('url', true);
11691 ctrl
.$setValidity('url', false);
11696 ctrl
.$formatters
.push(urlValidator
);
11697 ctrl
.$parsers
.push(urlValidator
);
11700 function emailInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
) {
11701 textInputType(scope
, element
, attr
, ctrl
, $sniffer
, $browser
);
11703 var emailValidator = function(value
) {
11704 if (isEmpty(value
) || EMAIL_REGEXP
.test(value
)) {
11705 ctrl
.$setValidity('email', true);
11708 ctrl
.$setValidity('email', false);
11713 ctrl
.$formatters
.push(emailValidator
);
11714 ctrl
.$parsers
.push(emailValidator
);
11717 function radioInputType(scope
, element
, attr
, ctrl
) {
11718 // make the name unique, if not defined
11719 if (isUndefined(attr
.name
)) {
11720 element
.attr('name', nextUid());
11723 element
.bind('click', function() {
11724 if (element
[0].checked
) {
11725 scope
.$apply(function() {
11726 ctrl
.$setViewValue(attr
.value
);
11731 ctrl
.$render = function() {
11732 var value
= attr
.value
;
11733 element
[0].checked
= (value
== ctrl
.$viewValue
);
11736 attr
.$observe('value', ctrl
.$render
);
11739 function checkboxInputType(scope
, element
, attr
, ctrl
) {
11740 var trueValue
= attr
.ngTrueValue
,
11741 falseValue
= attr
.ngFalseValue
;
11743 if (!isString(trueValue
)) trueValue
= true;
11744 if (!isString(falseValue
)) falseValue
= false;
11746 element
.bind('click', function() {
11747 scope
.$apply(function() {
11748 ctrl
.$setViewValue(element
[0].checked
);
11752 ctrl
.$render = function() {
11753 element
[0].checked
= ctrl
.$viewValue
;
11756 ctrl
.$formatters
.push(function(value
) {
11757 return value
=== trueValue
;
11760 ctrl
.$parsers
.push(function(value
) {
11761 return value
? trueValue : falseValue
;
11768 * @name ng.directive:textarea
11772 * HTML textarea element control with angular data-binding. The data-binding and validation
11773 * properties of this element are exactly the same as those of the
11774 * {@link ng.directive:input input element}.
11776 * @param {string} ngModel Assignable angular expression to data-bind to.
11777 * @param {string=} name Property name of the form under which the control is published.
11778 * @param {string=} required Sets `required` validation error key if the value is not entered.
11779 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
11780 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
11781 * `required` when you want to data-bind to the `required` attribute.
11782 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11784 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11786 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
11787 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
11788 * patterns defined as scope expressions.
11789 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11790 * interaction with the input element.
11796 * @name ng.directive:input
11800 * HTML input element control with angular data-binding. Input control follows HTML5 input types
11801 * and polyfills the HTML5 validation behavior for older browsers.
11803 * @param {string} ngModel Assignable angular expression to data-bind to.
11804 * @param {string=} name Property name of the form under which the control is published.
11805 * @param {string=} required Sets `required` validation error key if the value is not entered.
11806 * @param {boolean=} ngRequired Sets `required` attribute if set to true
11807 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
11809 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11811 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
11812 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
11813 * patterns defined as scope expressions.
11814 * @param {string=} ngChange Angular expression to be executed when input changes due to user
11815 * interaction with the input element.
11821 function Ctrl($scope) {
11822 $scope.user = {name: 'guest', last: 'visitor'};
11825 <div ng-controller="Ctrl">
11826 <form name="myForm">
11827 User name: <input type="text" name="userName" ng-model="user.name" required>
11828 <span class="error" ng-show="myForm.userName.$error.required">
11829 Required!</span><br>
11830 Last name: <input type="text" name="lastName" ng-model="user.last"
11831 ng-minlength="3" ng-maxlength="10">
11832 <span class="error" ng-show="myForm.lastName.$error.minlength">
11834 <span class="error" ng-show="myForm.lastName.$error.maxlength">
11835 Too long!</span><br>
11838 <tt>user = {{user}}</tt><br/>
11839 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br>
11840 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br>
11841 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br>
11842 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br>
11843 <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
11844 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
11845 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br>
11846 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
11850 it('should initialize to model', function() {
11851 expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
11852 expect(binding('myForm.userName.$valid')).toEqual('true');
11853 expect(binding('myForm.$valid')).toEqual('true');
11856 it('should be invalid if empty when required', function() {
11857 input('user.name').enter('');
11858 expect(binding('user')).toEqual('{"last":"visitor"}');
11859 expect(binding('myForm.userName.$valid')).toEqual('false');
11860 expect(binding('myForm.$valid')).toEqual('false');
11863 it('should be valid if empty when min length is set', function() {
11864 input('user.last').enter('');
11865 expect(binding('user')).toEqual('{"name":"guest","last":""}');
11866 expect(binding('myForm.lastName.$valid')).toEqual('true');
11867 expect(binding('myForm.$valid')).toEqual('true');
11870 it('should be invalid if less than required min length', function() {
11871 input('user.last').enter('xx');
11872 expect(binding('user')).toEqual('{"name":"guest"}');
11873 expect(binding('myForm.lastName.$valid')).toEqual('false');
11874 expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
11875 expect(binding('myForm.$valid')).toEqual('false');
11878 it('should be invalid if longer than max length', function() {
11879 input('user.last').enter('some ridiculously long name');
11880 expect(binding('user'))
11881 .toEqual('{"name":"guest"}');
11882 expect(binding('myForm.lastName.$valid')).toEqual('false');
11883 expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
11884 expect(binding('myForm.$valid')).toEqual('false');
11889 var inputDirective
= ['$browser', '$sniffer', function($browser
, $sniffer
) {
11892 require: '?ngModel',
11893 link: function(scope
, element
, attr
, ctrl
) {
11895 (inputType
[lowercase(attr
.type
)] || inputType
.text
)(scope
, element
, attr
, ctrl
, $sniffer
,
11902 var VALID_CLASS
= 'ng-valid',
11903 INVALID_CLASS
= 'ng-invalid',
11904 PRISTINE_CLASS
= 'ng-pristine',
11905 DIRTY_CLASS
= 'ng-dirty';
11909 * @name ng.directive:ngModel.NgModelController
11911 * @property {string} $viewValue Actual string value in the view.
11912 * @property {*} $modelValue The value in the model, that the control is bound to.
11913 * @property {Array.<Function>} $parsers Whenever the control reads value from the DOM, it executes
11914 * all of these functions to sanitize / convert the value as well as validate.
11916 * @property {Array.<Function>} $formatters Whenever the model value changes, it executes all of
11917 * these functions to convert the value as well as validate.
11919 * @property {Object} $error An bject hash with all errors as keys.
11921 * @property {boolean} $pristine True if user has not interacted with the control yet.
11922 * @property {boolean} $dirty True if user has already interacted with the control.
11923 * @property {boolean} $valid True if there is no error.
11924 * @property {boolean} $invalid True if at least one error on the control.
11928 * `NgModelController` provides API for the `ng-model` directive. The controller contains
11929 * services for data-binding, validation, CSS update, value formatting and parsing. It
11930 * specifically does not contain any logic which deals with DOM rendering or listening to
11931 * DOM events. The `NgModelController` is meant to be extended by other directives where, the
11932 * directive provides DOM manipulation and the `NgModelController` provides the data-binding.
11934 * This example shows how to use `NgModelController` with a custom control to achieve
11935 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
11936 * collaborate together to achieve the desired result.
11938 * <example module="customControl">
11939 <file name="style.css">
11940 [contenteditable] {
11941 border: 1px solid black;
11942 background-color: white;
11947 border: 1px solid red;
11951 <file name="script.js">
11952 angular.module('customControl', []).
11953 directive('contenteditable', function() {
11955 restrict: 'A', // only activate on element attribute
11956 require: '?ngModel', // get a hold of NgModelController
11957 link: function(scope, element, attrs, ngModel) {
11958 if(!ngModel) return; // do nothing if no ng-model
11960 // Specify how UI should be updated
11961 ngModel.$render = function() {
11962 element.html(ngModel.$viewValue || '');
11965 // Listen for change events to enable binding
11966 element.bind('blur keyup change', function() {
11967 scope.$apply(read);
11969 read(); // initialize
11971 // Write data to the model
11973 ngModel.$setViewValue(element.html());
11979 <file name="index.html">
11980 <form name="myForm">
11981 <div contenteditable
11982 name="myWidget" ng-model="userContent"
11983 required>Change me!</div>
11984 <span ng-show="myForm.myWidget.$error.required">Required!</span>
11986 <textarea ng-model="userContent"></textarea>
11989 <file name="scenario.js">
11990 it('should data-bind and become invalid', function() {
11991 var contentEditable = element('[contenteditable]');
11993 expect(contentEditable.text()).toEqual('Change me!');
11994 input('userContent').enter('');
11995 expect(contentEditable.text()).toEqual('');
11996 expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
12002 var NgModelController
= ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
12003 function($scope
, $exceptionHandler
, $attr
, $element
, $parse
) {
12004 this.$viewValue
= Number
.NaN
;
12005 this.$modelValue
= Number
.NaN
;
12006 this.$parsers
= [];
12007 this.$formatters
= [];
12008 this.$viewChangeListeners
= [];
12009 this.$pristine
= true;
12010 this.$dirty
= false;
12011 this.$valid
= true;
12012 this.$invalid
= false;
12013 this.$name
= $attr
.name
;
12015 var ngModelGet
= $parse($attr
.ngModel
),
12016 ngModelSet
= ngModelGet
.assign
;
12019 throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION
+ $attr
.ngModel
+
12020 ' (' + startingTag($element
) + ')');
12025 * @name ng.directive:ngModel.NgModelController#$render
12026 * @methodOf ng.directive:ngModel.NgModelController
12029 * Called when the view needs to be updated. It is expected that the user of the ng-model
12030 * directive will implement this method.
12032 this.$render
= noop
;
12034 var parentForm
= $element
.inheritedData('$formController') || nullFormCtrl
,
12035 invalidCount
= 0, // used to easily determine if we are valid
12036 $error
= this.$error
= {}; // keep invalid keys here
12039 // Setup initial state of the control
12040 $element
.addClass(PRISTINE_CLASS
);
12041 toggleValidCss(true);
12043 // convenience method for easy toggling of classes
12044 function toggleValidCss(isValid
, validationErrorKey
) {
12045 validationErrorKey
= validationErrorKey
? '-' + snake_case(validationErrorKey
, '-') : '';
12047 removeClass((isValid
? INVALID_CLASS : VALID_CLASS
) + validationErrorKey
).
12048 addClass((isValid
? VALID_CLASS : INVALID_CLASS
) + validationErrorKey
);
12053 * @name ng.directive:ngModel.NgModelController#$setValidity
12054 * @methodOf ng.directive:ngModel.NgModelController
12057 * Change the validity state, and notifies the form when the control changes validity. (i.e. it
12058 * does not notify form if given validator is already marked as invalid).
12060 * This method should be called by validators - i.e. the parser or formatter functions.
12062 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
12063 * to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
12064 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
12065 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
12066 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
12067 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
12069 this.$setValidity = function(validationErrorKey
, isValid
) {
12070 if ($error
[validationErrorKey
] === !isValid
) return;
12073 if ($error
[validationErrorKey
]) invalidCount
--;
12074 if (!invalidCount
) {
12075 toggleValidCss(true);
12076 this.$valid
= true;
12077 this.$invalid
= false;
12080 toggleValidCss(false);
12081 this.$invalid
= true;
12082 this.$valid
= false;
12086 $error
[validationErrorKey
] = !isValid
;
12087 toggleValidCss(isValid
, validationErrorKey
);
12089 parentForm
.$setValidity(validationErrorKey
, isValid
, this);
12095 * @name ng.directive:ngModel.NgModelController#$setViewValue
12096 * @methodOf ng.directive:ngModel.NgModelController
12099 * Read a value from view.
12101 * This method should be called from within a DOM event handler.
12102 * For example {@link ng.directive:input input} or
12103 * {@link ng.directive:select select} directives call it.
12105 * It internally calls all `parsers` and if resulted value is valid, updates the model and
12106 * calls all registered change listeners.
12108 * @param {string} value Value from the view.
12110 this.$setViewValue = function(value
) {
12111 this.$viewValue
= value
;
12114 if (this.$pristine
) {
12115 this.$dirty
= true;
12116 this.$pristine
= false;
12117 $element
.removeClass(PRISTINE_CLASS
).addClass(DIRTY_CLASS
);
12118 parentForm
.$setDirty();
12121 forEach(this.$parsers
, function(fn
) {
12125 if (this.$modelValue
!== value
) {
12126 this.$modelValue
= value
;
12127 ngModelSet($scope
, value
);
12128 forEach(this.$viewChangeListeners
, function(listener
) {
12132 $exceptionHandler(e
);
12141 $scope
.$watch(function ngModelWatch() {
12142 var value
= ngModelGet($scope
);
12144 // if scope model value and ngModel value are out of sync
12145 if (ctrl
.$modelValue
!== value
) {
12147 var formatters
= ctrl
.$formatters
,
12148 idx
= formatters
.length
;
12150 ctrl
.$modelValue
= value
;
12152 value
= formatters
[idx
](value
);
12155 if (ctrl
.$viewValue
!== value
) {
12156 ctrl
.$viewValue
= value
;
12166 * @name ng.directive:ngModel
12171 * Is directive that tells Angular to do two-way data binding. It works together with `input`,
12172 * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well.
12174 * `ngModel` is responsible for:
12176 * - binding the view into the model, which other directives such as `input`, `textarea` or `select`
12178 * - providing validation behavior (i.e. required, number, email, url),
12179 * - keeping state of the control (valid/invalid, dirty/pristine, validation errors),
12180 * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`),
12181 * - register the control with parent {@link ng.directive:form form}.
12183 * For basic examples, how to use `ngModel`, see:
12185 * - {@link ng.directive:input input}
12186 * - {@link ng.directive:input.text text}
12187 * - {@link ng.directive:input.checkbox checkbox}
12188 * - {@link ng.directive:input.radio radio}
12189 * - {@link ng.directive:input.number number}
12190 * - {@link ng.directive:input.email email}
12191 * - {@link ng.directive:input.url url}
12192 * - {@link ng.directive:select select}
12193 * - {@link ng.directive:textarea textarea}
12196 var ngModelDirective = function() {
12198 require: ['ngModel', '^?form'],
12199 controller: NgModelController
,
12200 link: function(scope
, element
, attr
, ctrls
) {
12201 // notify others, especially parent forms
12203 var modelCtrl
= ctrls
[0],
12204 formCtrl
= ctrls
[1] || nullFormCtrl
;
12206 formCtrl
.$addControl(modelCtrl
);
12208 element
.bind('$destroy', function() {
12209 formCtrl
.$removeControl(modelCtrl
);
12218 * @name ng.directive:ngChange
12222 * Evaluate given expression when user changes the input.
12223 * The expression is not evaluated when the value change is coming from the model.
12225 * Note, this directive requires `ngModel` to be present.
12233 * function Controller($scope) {
12234 * $scope.counter = 0;
12235 * $scope.change = function() {
12236 * $scope.counter++;
12240 * <div ng-controller="Controller">
12241 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
12242 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
12243 * <label for="ng-change-example2">Confirmed</label><br />
12244 * debug = {{confirmed}}<br />
12245 * counter = {{counter}}
12249 * it('should evaluate the expression if changing from view', function() {
12250 * expect(binding('counter')).toEqual('0');
12251 * element('#ng-change-example1').click();
12252 * expect(binding('counter')).toEqual('1');
12253 * expect(binding('confirmed')).toEqual('true');
12256 * it('should not evaluate the expression if changing from model', function() {
12257 * element('#ng-change-example2').click();
12258 * expect(binding('counter')).toEqual('0');
12259 * expect(binding('confirmed')).toEqual('true');
12264 var ngChangeDirective
= valueFn({
12265 require: 'ngModel',
12266 link: function(scope
, element
, attr
, ctrl
) {
12267 ctrl
.$viewChangeListeners
.push(function() {
12268 scope
.$eval(attr
.ngChange
);
12274 var requiredDirective = function() {
12276 require: '?ngModel',
12277 link: function(scope
, elm
, attr
, ctrl
) {
12279 attr
.required
= true; // force truthy in case we are on non input element
12281 var validator = function(value
) {
12282 if (attr
.required
&& (isEmpty(value
) || value
=== false)) {
12283 ctrl
.$setValidity('required', false);
12286 ctrl
.$setValidity('required', true);
12291 ctrl
.$formatters
.push(validator
);
12292 ctrl
.$parsers
.unshift(validator
);
12294 attr
.$observe('required', function() {
12295 validator(ctrl
.$viewValue
);
12304 * @name ng.directive:ngList
12307 * Text input that converts between comma-separated string into an array of strings.
12310 * @param {string=} ngList optional delimiter that should be used to split the value. If
12311 * specified in form `/something/` then the value will be converted into a regular expression.
12317 function Ctrl($scope) {
12318 $scope.names = ['igor', 'misko', 'vojta'];
12321 <form name="myForm" ng-controller="Ctrl">
12322 List: <input name="namesInput" ng-model="names" ng-list required>
12323 <span class="error" ng-show="myForm.list.$error.required">
12325 <tt>names = {{names}}</tt><br/>
12326 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
12327 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
12328 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
12329 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
12333 it('should initialize to model', function() {
12334 expect(binding('names')).toEqual('["igor","misko","vojta"]');
12335 expect(binding('myForm.namesInput.$valid')).toEqual('true');
12338 it('should be invalid if empty', function() {
12339 input('names').enter('');
12340 expect(binding('names')).toEqual('[]');
12341 expect(binding('myForm.namesInput.$valid')).toEqual('false');
12346 var ngListDirective = function() {
12348 require: 'ngModel',
12349 link: function(scope
, element
, attr
, ctrl
) {
12350 var match
= /\/(.*)\//.exec(attr
.ngList
),
12351 separator
= match
&& new RegExp(match
[1]) || attr
.ngList
|| ',';
12353 var parse = function(viewValue
) {
12357 forEach(viewValue
.split(separator
), function(value
) {
12358 if (value
) list
.push(trim(value
));
12365 ctrl
.$parsers
.push(parse
);
12366 ctrl
.$formatters
.push(function(value
) {
12367 if (isArray(value
)) {
12368 return value
.join(', ');
12378 var CONSTANT_VALUE_REGEXP
= /^(true|false|\d+)$/;
12380 var ngValueDirective = function() {
12383 compile: function(tpl
, tplAttr
) {
12384 if (CONSTANT_VALUE_REGEXP
.test(tplAttr
.ngValue
)) {
12385 return function(scope
, elm
, attr
) {
12386 attr
.$set('value', scope
.$eval(attr
.ngValue
));
12389 return function(scope
, elm
, attr
) {
12390 scope
.$watch(attr
.ngValue
, function valueWatchAction(value
) {
12391 attr
.$set('value', value
, false);
12401 * @name ng.directive:ngBind
12404 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
12405 * with the value of a given expression, and to update the text content when the value of that
12406 * expression changes.
12408 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
12409 * `{{ expression }}` which is similar but less verbose.
12411 * One scenario in which the use of `ngBind` is preferred over `{{ expression }}` binding is when
12412 * it's desirable to put bindings into template that is momentarily displayed by the browser in its
12413 * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the
12414 * bindings invisible to the user while the page is loading.
12416 * An alternative solution to this problem would be using the
12417 * {@link ng.directive:ngCloak ngCloak} directive.
12421 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
12424 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
12428 function Ctrl($scope) {
12429 $scope.name = 'Whirled';
12432 <div ng-controller="Ctrl">
12433 Enter name: <input type="text" ng-model="name"><br>
12434 Hello <span ng-bind="name"></span>!
12438 it('should check ng-bind', function() {
12439 expect(using('.doc-example-live').binding('name')).toBe('Whirled');
12440 using('.doc-example-live').input('name').enter('world');
12441 expect(using('.doc-example-live').binding('name')).toBe('world');
12446 var ngBindDirective
= ngDirective(function(scope
, element
, attr
) {
12447 element
.addClass('ng-binding').data('$binding', attr
.ngBind
);
12448 scope
.$watch(attr
.ngBind
, function ngBindWatchAction(value
) {
12449 element
.text(value
== undefined ? '' : value
);
12456 * @name ng.directive:ngBindTemplate
12459 * The `ngBindTemplate` directive specifies that the element
12460 * text should be replaced with the template in ngBindTemplate.
12461 * Unlike ngBind the ngBindTemplate can contain multiple `{{` `}}`
12462 * expressions. (This is required since some HTML elements
12463 * can not have SPAN elements such as TITLE, or OPTION to name a few.)
12466 * @param {string} ngBindTemplate template of form
12467 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
12470 * Try it here: enter text in text box and watch the greeting change.
12474 function Ctrl($scope) {
12475 $scope.salutation = 'Hello';
12476 $scope.name = 'World';
12479 <div ng-controller="Ctrl">
12480 Salutation: <input type="text" ng-model="salutation"><br>
12481 Name: <input type="text" ng-model="name"><br>
12482 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
12486 it('should check ng-bind', function() {
12487 expect(using('.doc-example-live').binding('salutation')).
12489 expect(using('.doc-example-live').binding('name')).
12491 using('.doc-example-live').input('salutation').enter('Greetings');
12492 using('.doc-example-live').input('name').enter('user');
12493 expect(using('.doc-example-live').binding('salutation')).
12495 expect(using('.doc-example-live').binding('name')).
12501 var ngBindTemplateDirective
= ['$interpolate', function($interpolate
) {
12502 return function(scope
, element
, attr
) {
12503 // TODO: move this to scenario runner
12504 var interpolateFn
= $interpolate(element
.attr(attr
.$attr
.ngBindTemplate
));
12505 element
.addClass('ng-binding').data('$binding', interpolateFn
);
12506 attr
.$observe('ngBindTemplate', function(value
) {
12507 element
.text(value
);
12515 * @name ng.directive:ngBindHtmlUnsafe
12518 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
12519 * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if
12520 * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too
12521 * restrictive and when you absolutely trust the source of the content you are binding to.
12523 * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
12526 * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate.
12528 var ngBindHtmlUnsafeDirective
= [function() {
12529 return function(scope
, element
, attr
) {
12530 element
.addClass('ng-binding').data('$binding', attr
.ngBindHtmlUnsafe
);
12531 scope
.$watch(attr
.ngBindHtmlUnsafe
, function ngBindHtmlUnsafeWatchAction(value
) {
12532 element
.html(value
|| '');
12537 function classDirective(name
, selector
) {
12538 name
= 'ngClass' + name
;
12539 return ngDirective(function(scope
, element
, attr
) {
12540 var oldVal
= undefined;
12542 scope
.$watch(attr
[name
], ngClassWatchAction
, true);
12544 attr
.$observe('class', function(value
) {
12545 var ngClass
= scope
.$eval(attr
[name
]);
12546 ngClassWatchAction(ngClass
, ngClass
);
12550 if (name
!== 'ngClass') {
12551 scope
.$watch('$index', function($index
, old
$index
) {
12552 var mod
= $index
& 1;
12553 if (mod
!== old
$index
& 1) {
12554 if (mod
=== selector
) {
12555 addClass(scope
.$eval(attr
[name
]));
12557 removeClass(scope
.$eval(attr
[name
]));
12564 function ngClassWatchAction(newVal
) {
12565 if (selector
=== true || scope
.$index
% 2 === selector
) {
12566 if (oldVal
&& !equals(newVal
,oldVal
)) {
12567 removeClass(oldVal
);
12571 oldVal
= copy(newVal
);
12575 function removeClass(classVal
) {
12576 if (isObject(classVal
) && !isArray(classVal
)) {
12577 classVal
= map(classVal
, function(v
, k
) { if (v
) return k
});
12579 element
.removeClass(isArray(classVal
) ? classVal
.join(' ') : classVal
);
12583 function addClass(classVal
) {
12584 if (isObject(classVal
) && !isArray(classVal
)) {
12585 classVal
= map(classVal
, function(v
, k
) { if (v
) return k
});
12588 element
.addClass(isArray(classVal
) ? classVal
.join(' ') : classVal
);
12596 * @name ng.directive:ngClass
12599 * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an
12600 * expression that represents all classes to be added.
12602 * The directive won't add duplicate classes if a particular class was already set.
12604 * When the expression changes, the previously added classes are removed and only then the
12605 * new classes are added.
12608 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
12609 * of the evaluation can be a string representing space delimited class
12610 * names, an array, or a map of class names to boolean values.
12614 <file name="index.html">
12615 <input type="button" value="set" ng-click="myVar='my-class'">
12616 <input type="button" value="clear" ng-click="myVar=''">
12618 <span ng-class="myVar">Sample Text</span>
12620 <file name="style.css">
12625 <file name="scenario.js">
12626 it('should check ng-class', function() {
12627 expect(element('.doc-example-live span').prop('className')).not().
12628 toMatch(/my-class/);
12630 using('.doc-example-live').element(':button:first').click();
12632 expect(element('.doc-example-live span').prop('className')).
12633 toMatch(/my-class/);
12635 using('.doc-example-live').element(':button:last').click();
12637 expect(element('.doc-example-live span').prop('className')).not().
12638 toMatch(/my-class/);
12643 var ngClassDirective
= classDirective('', true);
12647 * @name ng.directive:ngClassOdd
12650 * The `ngClassOdd` and `ngClassEven` directives work exactly as
12651 * {@link ng.directive:ngClass ngClass}, except it works in
12652 * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
12654 * This directive can be applied only within a scope of an
12655 * {@link ng.directive:ngRepeat ngRepeat}.
12658 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
12659 * of the evaluation can be a string representing space delimited class names or an array.
12663 <file name="index.html">
12664 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
12665 <li ng-repeat="name in names">
12666 <span ng-class-odd="'odd'" ng-class-even="'even'">
12672 <file name="style.css">
12680 <file name="scenario.js">
12681 it('should check ng-class-odd and ng-class-even', function() {
12682 expect(element('.doc-example-live li:first span').prop('className')).
12684 expect(element('.doc-example-live li:last span').prop('className')).
12690 var ngClassOddDirective
= classDirective('Odd', 0);
12694 * @name ng.directive:ngClassEven
12697 * The `ngClassOdd` and `ngClassEven` directives work exactly as
12698 * {@link ng.directive:ngClass ngClass}, except it works in
12699 * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
12701 * This directive can be applied only within a scope of an
12702 * {@link ng.directive:ngRepeat ngRepeat}.
12705 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
12706 * result of the evaluation can be a string representing space delimited class names or an array.
12710 <file name="index.html">
12711 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
12712 <li ng-repeat="name in names">
12713 <span ng-class-odd="'odd'" ng-class-even="'even'">
12714 {{name}}
12719 <file name="style.css">
12727 <file name="scenario.js">
12728 it('should check ng-class-odd and ng-class-even', function() {
12729 expect(element('.doc-example-live li:first span').prop('className')).
12731 expect(element('.doc-example-live li:last span').prop('className')).
12737 var ngClassEvenDirective
= classDirective('Even', 1);
12741 * @name ng.directive:ngCloak
12744 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
12745 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
12746 * directive to avoid the undesirable flicker effect caused by the html template display.
12748 * The directive can be applied to the `<body>` element, but typically a fine-grained application is
12749 * prefered in order to benefit from progressive rendering of the browser view.
12751 * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and
12752 * `angular.min.js` files. Following is the css rule:
12755 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
12760 * When this css rule is loaded by the browser, all html elements (including their children) that
12761 * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive
12762 * during the compilation of the template it deletes the `ngCloak` element attribute, which
12763 * makes the compiled element visible.
12765 * For the best result, `angular.js` script must be loaded in the head section of the html file;
12766 * alternatively, the css rule (above) must be included in the external stylesheet of the
12769 * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
12770 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
12771 * class `ngCloak` in addition to `ngCloak` directive as shown in the example below.
12778 <div id="template1" ng-cloak>{{ 'hello' }}</div>
12779 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
12782 it('should remove the template directive and css class', function() {
12783 expect(element('.doc-example-live #template1').attr('ng-cloak')).
12784 not().toBeDefined();
12785 expect(element('.doc-example-live #template2').attr('ng-cloak')).
12786 not().toBeDefined();
12792 var ngCloakDirective
= ngDirective({
12793 compile: function(element
, attr
) {
12794 attr
.$set('ngCloak', undefined);
12795 element
.removeClass('ng-cloak');
12801 * @name ng.directive:ngController
12804 * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular
12805 * supports the principles behind the Model-View-Controller design pattern.
12807 * MVC components in angular:
12809 * * Model — The Model is data in scope properties; scopes are attached to the DOM.
12810 * * View — The template (HTML with data bindings) is rendered into the View.
12811 * * Controller — The `ngController` directive specifies a Controller class; the class has
12812 * methods that typically express the business logic behind the application.
12814 * Note that an alternative way to define controllers is via the {@link ng.$route $route} service.
12818 * @param {expression} ngController Name of a globally accessible constructor function or an
12819 * {@link guide/expression expression} that on the current scope evaluates to a
12820 * constructor function.
12823 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
12824 * greeting are methods declared on the controller (see source tab). These methods can
12825 * easily be called from the angular markup. Notice that the scope becomes the `this` for the
12826 * controller's instance. This allows for easy access to the view data from the controller. Also
12827 * notice that any changes to the data are automatically reflected in the View without the need
12828 * for a manual update.
12832 function SettingsController($scope) {
12833 $scope.name = "John Smith";
12834 $scope.contacts = [
12835 {type:'phone', value:'408 555 1212'},
12836 {type:'email', value:'john.smith@example.org'} ];
12838 $scope.greet = function() {
12842 $scope.addContact = function() {
12843 this.contacts.push({type:'email', value:'yourname@example.org'});
12846 $scope.removeContact = function(contactToRemove) {
12847 var index = this.contacts.indexOf(contactToRemove);
12848 this.contacts.splice(index, 1);
12851 $scope.clearContact = function(contact) {
12852 contact.type = 'phone';
12853 contact.value = '';
12857 <div ng-controller="SettingsController">
12858 Name: <input type="text" ng-model="name"/>
12859 [ <a href="" ng-click="greet()">greet</a> ]<br/>
12862 <li ng-repeat="contact in contacts">
12863 <select ng-model="contact.type">
12864 <option>phone</option>
12865 <option>email</option>
12867 <input type="text" ng-model="contact.value"/>
12868 [ <a href="" ng-click="clearContact(contact)">clear</a>
12869 | <a href="" ng-click="removeContact(contact)">X</a> ]
12871 <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
12876 it('should check controller', function() {
12877 expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
12878 expect(element('.doc-example-live li:nth-child(1) input').val())
12879 .toBe('408 555 1212');
12880 expect(element('.doc-example-live li:nth-child(2) input').val())
12881 .toBe('john.smith@example.org');
12883 element('.doc-example-live li:first a:contains("clear")').click();
12884 expect(element('.doc-example-live li:first input').val()).toBe('');
12886 element('.doc-example-live li:last a:contains("add")').click();
12887 expect(element('.doc-example-live li:nth-child(3) input').val())
12888 .toBe('yourname@example.org');
12893 var ngControllerDirective
= [function() {
12902 * @name ng.directive:ngCsp
12907 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
12909 * This is necessary when developing things like Google Chrome Extensions.
12911 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things).
12912 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating
12913 * any of these restrictions.
12915 * AngularJS uses `Function(string)` generated functions as a speed optimization. By applying `ngCsp`
12916 * it is be possible to opt into the CSP compatible mode. When this mode is on AngularJS will
12917 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will
12920 * In order to use this feature put `ngCsp` directive on the root element of the application.
12923 * This example shows how to apply the `ngCsp` directive to the `html` tag.
12926 <html ng-app ng-csp>
12933 var ngCspDirective
= ['$sniffer', function($sniffer
) {
12936 compile: function() {
12937 $sniffer
.csp
= true;
12944 * @name ng.directive:ngClick
12947 * The ngClick allows you to specify custom behavior when
12948 * element is clicked.
12951 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
12952 * click. (Event object is available as `$event`)
12957 <button ng-click="count = count + 1" ng-init="count=0">
12963 it('should check ng-click', function() {
12964 expect(binding('count')).toBe('0');
12965 element('.doc-example-live :button').click();
12966 expect(binding('count')).toBe('1');
12972 * A directive that allows creation of custom onclick handlers that are defined as angular
12973 * expressions and are compiled and executed within the current scope.
12975 * Events that are handled via these handler are always configured not to propagate further.
12977 var ngEventDirectives
= {};
12979 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
12981 var directiveName
= directiveNormalize('ng-' + name
);
12982 ngEventDirectives
[directiveName
] = ['$parse', function($parse
) {
12983 return function(scope
, element
, attr
) {
12984 var fn
= $parse(attr
[directiveName
]);
12985 element
.bind(lowercase(name
), function(event
) {
12986 scope
.$apply(function() {
12987 fn(scope
, {$event:event
});
12997 * @name ng.directive:ngDblclick
13000 * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
13003 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
13004 * dblclick. (Event object is available as `$event`)
13007 * See {@link ng.directive:ngClick ngClick}
13013 * @name ng.directive:ngMousedown
13016 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
13019 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
13020 * mousedown. (Event object is available as `$event`)
13023 * See {@link ng.directive:ngClick ngClick}
13029 * @name ng.directive:ngMouseup
13032 * Specify custom behavior on mouseup event.
13035 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
13036 * mouseup. (Event object is available as `$event`)
13039 * See {@link ng.directive:ngClick ngClick}
13044 * @name ng.directive:ngMouseover
13047 * Specify custom behavior on mouseover event.
13050 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
13051 * mouseover. (Event object is available as `$event`)
13054 * See {@link ng.directive:ngClick ngClick}
13060 * @name ng.directive:ngMouseenter
13063 * Specify custom behavior on mouseenter event.
13066 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
13067 * mouseenter. (Event object is available as `$event`)
13070 * See {@link ng.directive:ngClick ngClick}
13076 * @name ng.directive:ngMouseleave
13079 * Specify custom behavior on mouseleave event.
13082 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
13083 * mouseleave. (Event object is available as `$event`)
13086 * See {@link ng.directive:ngClick ngClick}
13092 * @name ng.directive:ngMousemove
13095 * Specify custom behavior on mousemove event.
13098 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
13099 * mousemove. (Event object is available as `$event`)
13102 * See {@link ng.directive:ngClick ngClick}
13108 * @name ng.directive:ngSubmit
13111 * Enables binding angular expressions to onsubmit events.
13113 * Additionally it prevents the default action (which for form means sending the request to the
13114 * server and reloading the current page).
13117 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
13123 function Ctrl($scope) {
13125 $scope.text = 'hello';
13126 $scope.submit = function() {
13128 this.list.push(this.text);
13134 <form ng-submit="submit()" ng-controller="Ctrl">
13135 Enter text and hit enter:
13136 <input type="text" ng-model="text" name="text" />
13137 <input type="submit" id="submit" value="Submit" />
13138 <pre>list={{list}}</pre>
13142 it('should check ng-submit', function() {
13143 expect(binding('list')).toBe('[]');
13144 element('.doc-example-live #submit').click();
13145 expect(binding('list')).toBe('["hello"]');
13146 expect(input('text').val()).toBe('');
13148 it('should ignore empty strings', function() {
13149 expect(binding('list')).toBe('[]');
13150 element('.doc-example-live #submit').click();
13151 element('.doc-example-live #submit').click();
13152 expect(binding('list')).toBe('["hello"]');
13157 var ngSubmitDirective
= ngDirective(function(scope
, element
, attrs
) {
13158 element
.bind('submit', function() {
13159 scope
.$apply(attrs
.ngSubmit
);
13165 * @name ng.directive:ngInclude
13169 * Fetches, compiles and includes an external HTML fragment.
13171 * Keep in mind that Same Origin Policy applies to included resources
13172 * (e.g. ngInclude won't work for cross-domain requests on all browsers and for
13173 * file:// access on some browsers).
13177 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
13178 * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
13179 * @param {string=} onload Expression to evaluate when a new partial is loaded.
13181 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
13182 * $anchorScroll} to scroll the viewport after the content is loaded.
13184 * - If the attribute is not set, disable scrolling.
13185 * - If the attribute is set without value, enable scrolling.
13186 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
13190 <file name="index.html">
13191 <div ng-controller="Ctrl">
13192 <select ng-model="template" ng-options="t.name for t in templates">
13193 <option value="">(blank)</option>
13195 url of the template: <tt>{{template.url}}</tt>
13197 <div ng-include src="template.url"></div>
13200 <file name="script.js">
13201 function Ctrl($scope) {
13203 [ { name: 'template1.html', url: 'template1.html'}
13204 , { name: 'template2.html', url: 'template2.html'} ];
13205 $scope.template = $scope.templates[0];
13208 <file name="template1.html">
13209 Content of template1.html
13211 <file name="template2.html">
13212 Content of template2.html
13214 <file name="scenario.js">
13215 it('should load template1.html', function() {
13216 expect(element('.doc-example-live [ng-include]').text()).
13217 toMatch(/Content of template1.html/);
13219 it('should load template2.html', function() {
13220 select('template').option('1');
13221 expect(element('.doc-example-live [ng-include]').text()).
13222 toMatch(/Content of template2.html/);
13224 it('should change to blank', function() {
13225 select('template').option('');
13226 expect(element('.doc-example-live [ng-include]').text()).toEqual('');
13235 * @name ng.directive:ngInclude#$includeContentLoaded
13236 * @eventOf ng.directive:ngInclude
13237 * @eventType emit on the current ngInclude scope
13239 * Emitted every time the ngInclude content is reloaded.
13241 var ngIncludeDirective
= ['$http', '$templateCache', '$anchorScroll', '$compile',
13242 function($http
, $templateCache
, $anchorScroll
, $compile
) {
13246 compile: function(element
, attr
) {
13247 var srcExp
= attr
.ngInclude
|| attr
.src
,
13248 onloadExp
= attr
.onload
|| '',
13249 autoScrollExp
= attr
.autoscroll
;
13251 return function(scope
, element
) {
13252 var changeCounter
= 0,
13255 var clearContent = function() {
13257 childScope
.$destroy();
13264 scope
.$watch(srcExp
, function ngIncludeWatchAction(src
) {
13265 var thisChangeId
= ++changeCounter
;
13268 $http
.get(src
, {cache: $templateCache
}).success(function(response
) {
13269 if (thisChangeId
!== changeCounter
) return;
13271 if (childScope
) childScope
.$destroy();
13272 childScope
= scope
.$new();
13274 element
.html(response
);
13275 $compile(element
.contents())(childScope
);
13277 if (isDefined(autoScrollExp
) && (!autoScrollExp
|| scope
.$eval(autoScrollExp
))) {
13281 childScope
.$emit('$includeContentLoaded');
13282 scope
.$eval(onloadExp
);
13283 }).error(function() {
13284 if (thisChangeId
=== changeCounter
) clearContent();
13286 } else clearContent();
13295 * @name ng.directive:ngInit
13298 * The `ngInit` directive specifies initialization tasks to be executed
13299 * before the template enters execution mode during bootstrap.
13302 * @param {expression} ngInit {@link guide/expression Expression} to eval.
13307 <div ng-init="greeting='Hello'; person='World'">
13308 {{greeting}} {{person}}!
13312 it('should check greeting', function() {
13313 expect(binding('greeting')).toBe('Hello');
13314 expect(binding('person')).toBe('World');
13319 var ngInitDirective
= ngDirective({
13320 compile: function() {
13322 pre: function(scope
, element
, attrs
) {
13323 scope
.$eval(attrs
.ngInit
);
13331 * @name ng.directive:ngNonBindable
13335 * Sometimes it is necessary to write code which looks like bindings but which should be left alone
13336 * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML.
13341 * In this example there are two location where a simple binding (`{{}}`) is present, but the one
13342 * wrapped in `ngNonBindable` is left alone.
13347 <div>Normal: {{1 + 2}}</div>
13348 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
13351 it('should check ng-non-bindable', function() {
13352 expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
13353 expect(using('.doc-example-live').element('div:last').text()).
13359 var ngNonBindableDirective
= ngDirective({ terminal: true, priority: 1000 });
13363 * @name ng.directive:ngPluralize
13368 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
13369 * These rules are bundled with angular.js and the rules can be overridden
13370 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
13371 * by specifying the mappings between
13372 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
13373 * plural categories} and the strings to be displayed.
13375 * # Plural categories and explicit number rules
13377 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
13378 * plural categories} in Angular's default en-US locale: "one" and "other".
13380 * While a pural category may match many numbers (for example, in en-US locale, "other" can match
13381 * any number that is not 1), an explicit number rule can only match one number. For example, the
13382 * explicit number rule for "3" matches the number 3. You will see the use of plural categories
13383 * and explicit number rules throughout later parts of this documentation.
13385 * # Configuring ngPluralize
13386 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
13387 * You can also provide an optional attribute, `offset`.
13389 * The value of the `count` attribute can be either a string or an {@link guide/expression
13390 * Angular expression}; these are evaluated on the current scope for its bound value.
13392 * The `when` attribute specifies the mappings between plural categories and the actual
13393 * string to be displayed. The value of the attribute should be a JSON object so that Angular
13394 * can interpret it correctly.
13396 * The following example shows how to configure ngPluralize:
13399 * <ng-pluralize count="personCount"
13400 when="{'0': 'Nobody is viewing.',
13401 * 'one': '1 person is viewing.',
13402 * 'other': '{} people are viewing.'}">
13406 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
13407 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
13408 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
13409 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
13410 * show "a dozen people are viewing".
13412 * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
13413 * into pluralized strings. In the previous example, Angular will replace `{}` with
13414 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
13415 * for <span ng-non-bindable>{{numberExpression}}</span>.
13417 * # Configuring ngPluralize with offset
13418 * The `offset` attribute allows further customization of pluralized text, which can result in
13419 * a better user experience. For example, instead of the message "4 people are viewing this document",
13420 * you might display "John, Kate and 2 others are viewing this document".
13421 * The offset attribute allows you to offset a number by any desired value.
13422 * Let's take a look at an example:
13425 * <ng-pluralize count="personCount" offset=2
13426 * when="{'0': 'Nobody is viewing.',
13427 * '1': '{{person1}} is viewing.',
13428 * '2': '{{person1}} and {{person2}} are viewing.',
13429 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
13430 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
13434 * Notice that we are still using two plural categories(one, other), but we added
13435 * three explicit number rules 0, 1 and 2.
13436 * When one person, perhaps John, views the document, "John is viewing" will be shown.
13437 * When three people view the document, no explicit number rule is found, so
13438 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
13439 * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
13442 * Note that when you specify offsets, you must provide explicit number rules for
13443 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
13444 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
13445 * plural categories "one" and "other".
13447 * @param {string|expression} count The variable to be bounded to.
13448 * @param {string} when The mapping between plural category to its correspoding strings.
13449 * @param {number=} offset Offset to deduct from the total number.
13455 function Ctrl($scope) {
13456 $scope.person1 = 'Igor';
13457 $scope.person2 = 'Misko';
13458 $scope.personCount = 1;
13461 <div ng-controller="Ctrl">
13462 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/>
13463 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/>
13464 Number of People:<input type="text" ng-model="personCount" value="1" /><br/>
13466 <!--- Example with simple pluralization rules for en locale --->
13468 <ng-pluralize count="personCount"
13469 when="{'0': 'Nobody is viewing.',
13470 'one': '1 person is viewing.',
13471 'other': '{} people are viewing.'}">
13472 </ng-pluralize><br>
13474 <!--- Example with offset --->
13476 <ng-pluralize count="personCount" offset=2
13477 when="{'0': 'Nobody is viewing.',
13478 '1': '{{person1}} is viewing.',
13479 '2': '{{person1}} and {{person2}} are viewing.',
13480 'one': '{{person1}}, {{person2}} and one other person are viewing.',
13481 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
13486 it('should show correct pluralized string', function() {
13487 expect(element('.doc-example-live ng-pluralize:first').text()).
13488 toBe('1 person is viewing.');
13489 expect(element('.doc-example-live ng-pluralize:last').text()).
13490 toBe('Igor is viewing.');
13492 using('.doc-example-live').input('personCount').enter('0');
13493 expect(element('.doc-example-live ng-pluralize:first').text()).
13494 toBe('Nobody is viewing.');
13495 expect(element('.doc-example-live ng-pluralize:last').text()).
13496 toBe('Nobody is viewing.');
13498 using('.doc-example-live').input('personCount').enter('2');
13499 expect(element('.doc-example-live ng-pluralize:first').text()).
13500 toBe('2 people are viewing.');
13501 expect(element('.doc-example-live ng-pluralize:last').text()).
13502 toBe('Igor and Misko are viewing.');
13504 using('.doc-example-live').input('personCount').enter('3');
13505 expect(element('.doc-example-live ng-pluralize:first').text()).
13506 toBe('3 people are viewing.');
13507 expect(element('.doc-example-live ng-pluralize:last').text()).
13508 toBe('Igor, Misko and one other person are viewing.');
13510 using('.doc-example-live').input('personCount').enter('4');
13511 expect(element('.doc-example-live ng-pluralize:first').text()).
13512 toBe('4 people are viewing.');
13513 expect(element('.doc-example-live ng-pluralize:last').text()).
13514 toBe('Igor, Misko and 2 other people are viewing.');
13517 it('should show data-binded names', function() {
13518 using('.doc-example-live').input('personCount').enter('4');
13519 expect(element('.doc-example-live ng-pluralize:last').text()).
13520 toBe('Igor, Misko and 2 other people are viewing.');
13522 using('.doc-example-live').input('person1').enter('Di');
13523 using('.doc-example-live').input('person2').enter('Vojta');
13524 expect(element('.doc-example-live ng-pluralize:last').text()).
13525 toBe('Di, Vojta and 2 other people are viewing.');
13530 var ngPluralizeDirective
= ['$locale', '$interpolate', function($locale
, $interpolate
) {
13534 link: function(scope
, element
, attr
) {
13535 var numberExp
= attr
.count
,
13536 whenExp
= element
.attr(attr
.$attr
.when
), // this is because we have {{}} in attrs
13537 offset
= attr
.offset
|| 0,
13538 whens
= scope
.$eval(whenExp
),
13540 startSymbol
= $interpolate
.startSymbol(),
13541 endSymbol
= $interpolate
.endSymbol();
13543 forEach(whens
, function(expression
, key
) {
13545 $interpolate(expression
.replace(BRACE
, startSymbol
+ numberExp
+ '-' +
13546 offset
+ endSymbol
));
13549 scope
.$watch(function ngPluralizeWatch() {
13550 var value
= parseFloat(scope
.$eval(numberExp
));
13552 if (!isNaN(value
)) {
13553 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
13554 //check it against pluralization rules in $locale service
13555 if (!(value
in whens
)) value
= $locale
.pluralCat(value
- offset
);
13556 return whensExpFns
[value
](scope
, element
, true);
13560 }, function ngPluralizeWatchAction(newVal
) {
13561 element
.text(newVal
);
13569 * @name ng.directive:ngRepeat
13572 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
13573 * instance gets its own scope, where the given loop variable is set to the current collection item,
13574 * and `$index` is set to the item index or key.
13576 * Special properties are exposed on the local scope of each template instance, including:
13578 * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
13579 * * `$first` – `{boolean}` – true if the repeated element is first in the iterator.
13580 * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
13581 * * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
13587 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
13588 * formats are currently supported:
13590 * * `variable in expression` – where variable is the user defined loop variable and `expression`
13591 * is a scope expression giving the collection to enumerate.
13593 * For example: `track in cd.tracks`.
13595 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
13596 * and `expression` is the scope expression giving the collection to enumerate.
13598 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
13601 * This example initializes the scope to a list of names and
13602 * then uses `ngRepeat` to display every person:
13605 <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
13606 I have {{friends.length}} friends. They are:
13608 <li ng-repeat="friend in friends">
13609 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
13615 it('should check ng-repeat', function() {
13616 var r = using('.doc-example-live').repeater('ul li');
13617 expect(r.count()).toBe(2);
13618 expect(r.row(0)).toEqual(["1","John","25"]);
13619 expect(r.row(1)).toEqual(["2","Mary","28"]);
13624 var ngRepeatDirective
= ngDirective({
13625 transclude: 'element',
13628 compile: function(element
, attr
, linker
) {
13629 return function(scope
, iterStartElement
, attr
){
13630 var expression
= attr
.ngRepeat
;
13631 var match
= expression
.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
13632 lhs
, rhs
, valueIdent
, keyIdent
;
13634 throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
13635 expression
+ "'.");
13639 match
= lhs
.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
13641 throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
13644 valueIdent
= match
[3] || match
[1];
13645 keyIdent
= match
[2];
13647 // Store a list of elements from previous run. This is a hash where key is the item from the
13648 // iterator, and the value is an array of objects with following properties.
13649 // - scope: bound scope
13650 // - element: previous element.
13651 // - index: position
13652 // We need an array of these objects since the same object can be returned from the iterator.
13653 // We expect this to be a rare case.
13654 var lastOrder
= new HashQueueMap();
13656 scope
.$watch(function ngRepeatWatch(scope
){
13658 collection
= scope
.$eval(rhs
),
13659 cursor
= iterStartElement
, // current position of the node
13660 // Same as lastOrder but it has the current state. It will become the
13661 // lastOrder on the next iteration.
13662 nextOrder
= new HashQueueMap(),
13665 key
, value
, // key/value of iteration
13667 last
; // last object information {scope, element, index}
13671 if (!isArray(collection
)) {
13672 // if object, extract keys, sort them and use to determine order of iteration over obj props
13674 for(key
in collection
) {
13675 if (collection
.hasOwnProperty(key
) && key
.charAt(0) != '$') {
13681 array
= collection
|| [];
13684 arrayBound
= array
.length
-1;
13686 // we are not using forEach for perf reasons (trying to avoid #call)
13687 for (index
= 0, length
= array
.length
; index
< length
; index
++) {
13688 key
= (collection
=== array
) ? index : array
[index
];
13689 value
= collection
[key
];
13691 last
= lastOrder
.shift(value
);
13694 // if we have already seen this object, then we need to reuse the
13695 // associated scope/element
13696 childScope
= last
.scope
;
13697 nextOrder
.push(value
, last
);
13699 if (index
=== last
.index
) {
13701 cursor
= last
.element
;
13703 // existing item which got moved
13704 last
.index
= index
;
13705 // This may be a noop, if the element is next, but I don't know of a good way to
13706 // figure this out, since it would require extra DOM access, so let's just hope that
13707 // the browsers realizes that it is noop, and treats it as such.
13708 cursor
.after(last
.element
);
13709 cursor
= last
.element
;
13712 // new item which we don't know about
13713 childScope
= scope
.$new();
13716 childScope
[valueIdent
] = value
;
13717 if (keyIdent
) childScope
[keyIdent
] = key
;
13718 childScope
.$index
= index
;
13720 childScope
.$first
= (index
=== 0);
13721 childScope
.$last
= (index
=== arrayBound
);
13722 childScope
.$middle
= !(childScope
.$first
|| childScope
.$last
);
13725 linker(childScope
, function(clone
){
13726 cursor
.after(clone
);
13729 element: (cursor
= clone
),
13732 nextOrder
.push(value
, last
);
13738 for (key
in lastOrder
) {
13739 if (lastOrder
.hasOwnProperty(key
)) {
13740 array
= lastOrder
[key
];
13741 while(array
.length
) {
13742 value
= array
.pop();
13743 value
.element
.remove();
13744 value
.scope
.$destroy();
13749 lastOrder
= nextOrder
;
13757 * @name ng.directive:ngShow
13760 * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
13764 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
13765 * then the element is shown or hidden respectively.
13770 Click me: <input type="checkbox" ng-model="checked"><br/>
13771 Show: <span ng-show="checked">I show up when your checkbox is checked.</span> <br/>
13772 Hide: <span ng-hide="checked">I hide when your checkbox is checked.</span>
13775 it('should check ng-show / ng-hide', function() {
13776 expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
13777 expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
13779 input('checked').check();
13781 expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
13782 expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
13787 //TODO(misko): refactor to remove element from the DOM
13788 var ngShowDirective
= ngDirective(function(scope
, element
, attr
){
13789 scope
.$watch(attr
.ngShow
, function ngShowWatchAction(value
){
13790 element
.css('display', toBoolean(value
) ? '' : 'none');
13797 * @name ng.directive:ngHide
13800 * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
13804 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
13805 * the element is shown or hidden respectively.
13810 Click me: <input type="checkbox" ng-model="checked"><br/>
13811 Show: <span ng-show="checked">I show up when you checkbox is checked?</span> <br/>
13812 Hide: <span ng-hide="checked">I hide when you checkbox is checked?</span>
13815 it('should check ng-show / ng-hide', function() {
13816 expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
13817 expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
13819 input('checked').check();
13821 expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
13822 expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
13827 //TODO(misko): refactor to remove element from the DOM
13828 var ngHideDirective
= ngDirective(function(scope
, element
, attr
){
13829 scope
.$watch(attr
.ngHide
, function ngHideWatchAction(value
){
13830 element
.css('display', toBoolean(value
) ? 'none' : '');
13836 * @name ng.directive:ngStyle
13839 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
13842 * @param {expression} ngStyle {@link guide/expression Expression} which evals to an
13843 * object whose keys are CSS style names and values are corresponding values for those CSS
13848 <file name="index.html">
13849 <input type="button" value="set" ng-click="myStyle={color:'red'}">
13850 <input type="button" value="clear" ng-click="myStyle={}">
13852 <span ng-style="myStyle">Sample Text</span>
13853 <pre>myStyle={{myStyle}}</pre>
13855 <file name="style.css">
13860 <file name="scenario.js">
13861 it('should check ng-style', function() {
13862 expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
13863 element('.doc-example-live :button[value=set]').click();
13864 expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
13865 element('.doc-example-live :button[value=clear]').click();
13866 expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
13871 var ngStyleDirective
= ngDirective(function(scope
, element
, attr
) {
13872 scope
.$watch(attr
.ngStyle
, function ngStyleWatchAction(newStyles
, oldStyles
) {
13873 if (oldStyles
&& (newStyles
!== oldStyles
)) {
13874 forEach(oldStyles
, function(val
, style
) { element
.css(style
, '');});
13876 if (newStyles
) element
.css(newStyles
);
13882 * @name ng.directive:ngSwitch
13886 * Conditionally change the DOM structure.
13889 * <ANY ng-switch="expression">
13890 * <ANY ng-switch-when="matchValue1">...</ANY>
13891 * <ANY ng-switch-when="matchValue2">...</ANY>
13893 * <ANY ng-switch-default>...</ANY>
13897 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
13898 * @paramDescription
13899 * On child elments add:
13901 * * `ngSwitchWhen`: the case statement to match against. If match then this
13902 * case will be displayed.
13903 * * `ngSwitchDefault`: the default case when no other casses match.
13909 function Ctrl($scope) {
13910 $scope.items = ['settings', 'home', 'other'];
13911 $scope.selection = $scope.items[0];
13914 <div ng-controller="Ctrl">
13915 <select ng-model="selection" ng-options="item for item in items">
13917 <tt>selection={{selection}}</tt>
13919 <div ng-switch on="selection" >
13920 <div ng-switch-when="settings">Settings Div</div>
13921 <span ng-switch-when="home">Home Span</span>
13922 <span ng-switch-default>default</span>
13927 it('should start in settings', function() {
13928 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
13930 it('should change to home', function() {
13931 select('selection').option('home');
13932 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
13934 it('should select deafault', function() {
13935 select('selection').option('other');
13936 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
13941 var NG_SWITCH
= 'ng-switch';
13942 var ngSwitchDirective
= valueFn({
13944 require: 'ngSwitch',
13945 // asks for $scope to fool the BC controller module
13946 controller: ['$scope', function ngSwitchController() {
13949 link: function(scope
, element
, attr
, ctrl
) {
13950 var watchExpr
= attr
.ngSwitch
|| attr
.on
,
13951 selectedTransclude
,
13955 scope
.$watch(watchExpr
, function ngSwitchWatchAction(value
) {
13956 if (selectedElement
) {
13957 selectedScope
.$destroy();
13958 selectedElement
.remove();
13959 selectedElement
= selectedScope
= null;
13961 if ((selectedTransclude
= ctrl
.cases
['!' + value
] || ctrl
.cases
['?'])) {
13962 scope
.$eval(attr
.change
);
13963 selectedScope
= scope
.$new();
13964 selectedTransclude(selectedScope
, function(caseElement
) {
13965 selectedElement
= caseElement
;
13966 element
.append(caseElement
);
13973 var ngSwitchWhenDirective
= ngDirective({
13974 transclude: 'element',
13976 require: '^ngSwitch',
13977 compile: function(element
, attrs
, transclude
) {
13978 return function(scope
, element
, attr
, ctrl
) {
13979 ctrl
.cases
['!' + attrs
.ngSwitchWhen
] = transclude
;
13984 var ngSwitchDefaultDirective
= ngDirective({
13985 transclude: 'element',
13987 require: '^ngSwitch',
13988 compile: function(element
, attrs
, transclude
) {
13989 return function(scope
, element
, attr
, ctrl
) {
13990 ctrl
.cases
['?'] = transclude
;
13997 * @name ng.directive:ngTransclude
14000 * Insert the transcluded DOM here.
14005 <doc:example module="transclude">
14008 function Ctrl($scope) {
14009 $scope.title = 'Lorem Ipsum';
14010 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
14013 angular.module('transclude', [])
14014 .directive('pane', function(){
14019 locals: { title:'bind' },
14020 template: '<div style="border: 1px solid black;">' +
14021 '<div style="background-color: gray">{{title}}</div>' +
14022 '<div ng-transclude></div>' +
14027 <div ng-controller="Ctrl">
14028 <input ng-model="title"><br>
14029 <textarea ng-model="text"></textarea> <br/>
14030 <pane title="{{title}}">{{text}}</pane>
14034 it('should have transcluded', function() {
14035 input('title').enter('TITLE');
14036 input('text').enter('TEXT');
14037 expect(binding('title')).toEqual('TITLE');
14038 expect(binding('text')).toEqual('TEXT');
14044 var ngTranscludeDirective
= ngDirective({
14045 controller: ['$transclude', '$element', function($transclude
, $element
) {
14046 $transclude(function(clone
) {
14047 $element
.append(clone
);
14054 * @name ng.directive:ngView
14059 * `ngView` is a directive that complements the {@link ng.$route $route} service by
14060 * including the rendered template of the current route into the main layout (`index.html`) file.
14061 * Every time the current route changes, the included view changes with it according to the
14062 * configuration of the `$route` service.
14066 <example module="ngView">
14067 <file name="index.html">
14068 <div ng-controller="MainCntl">
14070 <a href="Book/Moby">Moby</a> |
14071 <a href="Book/Moby/ch/1">Moby: Ch1</a> |
14072 <a href="Book/Gatsby">Gatsby</a> |
14073 <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
14074 <a href="Book/Scarlet">Scarlet Letter</a><br/>
14076 <div ng-view></div>
14079 <pre>$location.path() = {{$location.path()}}</pre>
14080 <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
14081 <pre>$route.current.params = {{$route.current.params}}</pre>
14082 <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
14083 <pre>$routeParams = {{$routeParams}}</pre>
14087 <file name="book.html">
14088 controller: {{name}}<br />
14089 Book Id: {{params.bookId}}<br />
14092 <file name="chapter.html">
14093 controller: {{name}}<br />
14094 Book Id: {{params.bookId}}<br />
14095 Chapter Id: {{params.chapterId}}
14098 <file name="script.js">
14099 angular.module('ngView', [], function($routeProvider, $locationProvider) {
14100 $routeProvider.when('/Book/:bookId', {
14101 templateUrl: 'book.html',
14102 controller: BookCntl
14104 $routeProvider.when('/Book/:bookId/ch/:chapterId', {
14105 templateUrl: 'chapter.html',
14106 controller: ChapterCntl
14109 // configure html5 to get links working on jsfiddle
14110 $locationProvider.html5Mode(true);
14113 function MainCntl($scope, $route, $routeParams, $location) {
14114 $scope.$route = $route;
14115 $scope.$location = $location;
14116 $scope.$routeParams = $routeParams;
14119 function BookCntl($scope, $routeParams) {
14120 $scope.name = "BookCntl";
14121 $scope.params = $routeParams;
14124 function ChapterCntl($scope, $routeParams) {
14125 $scope.name = "ChapterCntl";
14126 $scope.params = $routeParams;
14130 <file name="scenario.js">
14131 it('should load and compile correct template', function() {
14132 element('a:contains("Moby: Ch1")').click();
14133 var content = element('.doc-example-live [ng-view]').text();
14134 expect(content).toMatch(/controller\: ChapterCntl/);
14135 expect(content).toMatch(/Book Id\: Moby/);
14136 expect(content).toMatch(/Chapter Id\: 1/);
14138 element('a:contains("Scarlet")').click();
14139 content = element('.doc-example-live [ng-view]').text();
14140 expect(content).toMatch(/controller\: BookCntl/);
14141 expect(content).toMatch(/Book Id\: Scarlet/);
14150 * @name ng.directive:ngView#$viewContentLoaded
14151 * @eventOf ng.directive:ngView
14152 * @eventType emit on the current ngView scope
14154 * Emitted every time the ngView content is reloaded.
14156 var ngViewDirective
= ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
14158 function($http
, $templateCache
, $route
, $anchorScroll
, $compile
,
14163 link: function(scope
, element
, attr
) {
14165 onloadExp
= attr
.onload
|| '';
14167 scope
.$on('$routeChangeSuccess', update
);
14171 function destroyLastScope() {
14173 lastScope
.$destroy();
14178 function clearContent() {
14180 destroyLastScope();
14183 function update() {
14184 var locals
= $route
.current
&& $route
.current
.locals
,
14185 template
= locals
&& locals
.$template
;
14188 element
.html(template
);
14189 destroyLastScope();
14191 var link
= $compile(element
.contents()),
14192 current
= $route
.current
,
14195 lastScope
= current
.scope
= scope
.$new();
14196 if (current
.controller
) {
14197 locals
.$scope
= lastScope
;
14198 controller
= $controller(current
.controller
, locals
);
14199 element
.children().data('$ngControllerController', controller
);
14203 lastScope
.$emit('$viewContentLoaded');
14204 lastScope
.$eval(onloadExp
);
14206 // $anchorScroll might listen on event...
14218 * @name ng.directive:script
14221 * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
14222 * template can be used by `ngInclude`, `ngView` or directive templates.
14225 * @param {'text/ng-template'} type must be set to `'text/ng-template'`
14230 <script type="text/ng-template" id="/tpl.html">
14231 Content of the template.
14234 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
14235 <div id="tpl-content" ng-include src="currentTpl"></div>
14238 it('should load template defined inside script tag', function() {
14239 element('#tpl-link').click();
14240 expect(element('#tpl-content').text()).toMatch(/Content of the template/);
14245 var scriptDirective
= ['$templateCache', function($templateCache
) {
14249 compile: function(element
, attr
) {
14250 if (attr
.type
== 'text/ng-template') {
14251 var templateUrl
= attr
.id
,
14252 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
14253 text
= element
[0].text
;
14255 $templateCache
.put(templateUrl
, text
);
14263 * @name ng.directive:select
14267 * HTML `SELECT` element with angular data-binding.
14271 * Optionally `ngOptions` attribute can be used to dynamically generate a list of `<option>`
14272 * elements for a `<select>` element using an array or an object obtained by evaluating the
14273 * `ngOptions` expression.
14275 * When an item in the select menu is select, the value of array element or object property
14276 * represented by the selected option will be bound to the model identified by the `ngModel`
14277 * directive of the parent select element.
14279 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
14280 * be nested into the `<select>` element. This element will then represent `null` or "not selected"
14281 * option. See example below for demonstration.
14283 * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
14284 * of {@link ng.directive:ngRepeat ngRepeat} when you want the
14285 * `select` model to be bound to a non-string value. This is because an option element can currently
14286 * be bound to string values only.
14288 * @param {string} ngModel Assignable angular expression to data-bind to.
14289 * @param {string=} name Property name of the form under which the control is published.
14290 * @param {string=} required The control is considered valid only if value is entered.
14291 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
14292 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
14293 * `required` when you want to data-bind to the `required` attribute.
14294 * @param {comprehension_expression=} ngOptions in one of the following forms:
14296 * * for array data sources:
14297 * * `label` **`for`** `value` **`in`** `array`
14298 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
14299 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
14300 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
14301 * * for object data sources:
14302 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
14303 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
14304 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
14305 * * `select` **`as`** `label` **`group by`** `group`
14306 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
14310 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
14311 * * `value`: local variable which will refer to each item in the `array` or each property value
14312 * of `object` during iteration.
14313 * * `key`: local variable which will refer to a property name in `object` during iteration.
14314 * * `label`: The result of this expression will be the label for `<option>` element. The
14315 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
14316 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
14317 * element. If not specified, `select` expression will default to `value`.
14318 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
14325 function MyCntrl($scope) {
14327 {name:'black', shade:'dark'},
14328 {name:'white', shade:'light'},
14329 {name:'red', shade:'dark'},
14330 {name:'blue', shade:'dark'},
14331 {name:'yellow', shade:'light'}
14333 $scope.color = $scope.colors[2]; // red
14336 <div ng-controller="MyCntrl">
14338 <li ng-repeat="color in colors">
14339 Name: <input ng-model="color.name">
14340 [<a href ng-click="colors.splice($index, 1)">X</a>]
14343 [<a href ng-click="colors.push({})">add</a>]
14347 Color (null not allowed):
14348 <select ng-model="color" ng-options="c.name for c in colors"></select><br>
14350 Color (null allowed):
14351 <span class="nullable">
14352 <select ng-model="color" ng-options="c.name for c in colors">
14353 <option value="">-- chose color --</option>
14357 Color grouped by shade:
14358 <select ng-model="color" ng-options="c.name group by c.shade for c in colors">
14362 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
14364 Currently selected: {{ {selected_color:color} }}
14365 <div style="border:solid 1px black; height:20px"
14366 ng-style="{'background-color':color.name}">
14371 it('should check ng-options', function() {
14372 expect(binding('{selected_color:color}')).toMatch('red');
14373 select('color').option('0');
14374 expect(binding('{selected_color:color}')).toMatch('black');
14375 using('.nullable').select('color').option('');
14376 expect(binding('{selected_color:color}')).toMatch('null');
14382 var ngOptionsDirective
= valueFn({ terminal: true });
14383 var selectDirective
= ['$compile', '$parse', function($compile
, $parse
) {
14384 //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000077770
14385 var NG_OPTIONS_REGEXP
= /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,
14386 nullModelCtrl
= {$setViewValue: noop
};
14390 require: ['select', '?ngModel'],
14391 controller: ['$element', '$scope', '$attrs', function($element
, $scope
, $attrs
) {
14394 ngModelCtrl
= nullModelCtrl
,
14399 self
.databound
= $attrs
.ngModel
;
14402 self
.init = function(ngModelCtrl_
, nullOption_
, unknownOption_
) {
14403 ngModelCtrl
= ngModelCtrl_
;
14404 nullOption
= nullOption_
;
14405 unknownOption
= unknownOption_
;
14409 self
.addOption = function(value
) {
14410 optionsMap
[value
] = true;
14412 if (ngModelCtrl
.$viewValue
== value
) {
14413 $element
.val(value
);
14414 if (unknownOption
.parent()) unknownOption
.remove();
14419 self
.removeOption = function(value
) {
14420 if (this.hasOption(value
)) {
14421 delete optionsMap
[value
];
14422 if (ngModelCtrl
.$viewValue
== value
) {
14423 this.renderUnknownOption(value
);
14429 self
.renderUnknownOption = function(val
) {
14430 var unknownVal
= '? ' + hashKey(val
) + ' ?';
14431 unknownOption
.val(unknownVal
);
14432 $element
.prepend(unknownOption
);
14433 $element
.val(unknownVal
);
14434 unknownOption
.prop('selected', true); // needed for IE
14438 self
.hasOption = function(value
) {
14439 return optionsMap
.hasOwnProperty(value
);
14442 $scope
.$on('$destroy', function() {
14443 // disable unknown option so that we don't do work when the whole select is being destroyed
14444 self
.renderUnknownOption
= noop
;
14448 link: function(scope
, element
, attr
, ctrls
) {
14449 // if ngModel is not defined, we don't need to do anything
14450 if (!ctrls
[1]) return;
14452 var selectCtrl
= ctrls
[0],
14453 ngModelCtrl
= ctrls
[1],
14454 multiple
= attr
.multiple
,
14455 optionsExp
= attr
.ngOptions
,
14456 nullOption
= false, // if false, user will not be able to select it (used by ngOptions)
14458 // we can't just jqLite('<option>') since jqLite is not smart enough
14459 // to create it in <select> and IE barfs otherwise.
14460 optionTemplate
= jqLite(document
.createElement('option')),
14461 optGroupTemplate
=jqLite(document
.createElement('optgroup')),
14462 unknownOption
= optionTemplate
.clone();
14464 // find "null" option
14465 for(var i
= 0, children
= element
.children(), ii
= children
.length
; i
< ii
; i
++) {
14466 if (children
[i
].value
== '') {
14467 emptyOption
= nullOption
= children
.eq(i
);
14472 selectCtrl
.init(ngModelCtrl
, nullOption
, unknownOption
);
14474 // required validator
14475 if (multiple
&& (attr
.required
|| attr
.ngRequired
)) {
14476 var requiredValidator = function(value
) {
14477 ngModelCtrl
.$setValidity('required', !attr
.required
|| (value
&& value
.length
));
14481 ngModelCtrl
.$parsers
.push(requiredValidator
);
14482 ngModelCtrl
.$formatters
.unshift(requiredValidator
);
14484 attr
.$observe('required', function() {
14485 requiredValidator(ngModelCtrl
.$viewValue
);
14489 if (optionsExp
) Options(scope
, element
, ngModelCtrl
);
14490 else if (multiple
) Multiple(scope
, element
, ngModelCtrl
);
14491 else Single(scope
, element
, ngModelCtrl
, selectCtrl
);
14494 ////////////////////////////
14498 function Single(scope
, selectElement
, ngModelCtrl
, selectCtrl
) {
14499 ngModelCtrl
.$render = function() {
14500 var viewValue
= ngModelCtrl
.$viewValue
;
14502 if (selectCtrl
.hasOption(viewValue
)) {
14503 if (unknownOption
.parent()) unknownOption
.remove();
14504 selectElement
.val(viewValue
);
14505 if (viewValue
=== '') emptyOption
.prop('selected', true); // to make IE9 happy
14507 if (isUndefined(viewValue
) && emptyOption
) {
14508 selectElement
.val('');
14510 selectCtrl
.renderUnknownOption(viewValue
);
14515 selectElement
.bind('change', function() {
14516 scope
.$apply(function() {
14517 if (unknownOption
.parent()) unknownOption
.remove();
14518 ngModelCtrl
.$setViewValue(selectElement
.val());
14523 function Multiple(scope
, selectElement
, ctrl
) {
14525 ctrl
.$render = function() {
14526 var items
= new HashMap(ctrl
.$viewValue
);
14527 forEach(selectElement
.find('option'), function(option
) {
14528 option
.selected
= isDefined(items
.get(option
.value
));
14532 // we have to do it on each watch since ngModel watches reference, but
14533 // we need to work of an array, so we need to see if anything was inserted/removed
14534 scope
.$watch(function selectMultipleWatch() {
14535 if (!equals(lastView
, ctrl
.$viewValue
)) {
14536 lastView
= copy(ctrl
.$viewValue
);
14541 selectElement
.bind('change', function() {
14542 scope
.$apply(function() {
14544 forEach(selectElement
.find('option'), function(option
) {
14545 if (option
.selected
) {
14546 array
.push(option
.value
);
14549 ctrl
.$setViewValue(array
);
14554 function Options(scope
, selectElement
, ctrl
) {
14557 if (! (match
= optionsExp
.match(NG_OPTIONS_REGEXP
))) {
14559 "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
14560 " but got '" + optionsExp
+ "'.");
14563 var displayFn
= $parse(match
[2] || match
[1]),
14564 valueName
= match
[4] || match
[6],
14565 keyName
= match
[5],
14566 groupByFn
= $parse(match
[3] || ''),
14567 valueFn
= $parse(match
[2] ? match
[1] : valueName
),
14568 valuesFn
= $parse(match
[7]),
14569 // This is an array of array of existing option groups in DOM. We try to reuse these if possible
14570 // optionGroupsCache[0] is the options with no option group
14571 // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
14572 optionGroupsCache
= [[{element: selectElement
, label:''}]];
14575 // compile the element since there might be bindings in it
14576 $compile(nullOption
)(scope
);
14578 // remove the class, which is added automatically because we recompile the element and it
14579 // becomes the compilation root
14580 nullOption
.removeClass('ng-scope');
14582 // we need to remove it before calling selectElement.html('') because otherwise IE will
14583 // remove the label from the element. wtf?
14584 nullOption
.remove();
14587 // clear contents, we'll add what's needed based on the model
14588 selectElement
.html('');
14590 selectElement
.bind('change', function() {
14591 scope
.$apply(function() {
14593 collection
= valuesFn(scope
) || [],
14595 key
, value
, optionElement
, index
, groupIndex
, length
, groupLength
;
14599 for (groupIndex
= 0, groupLength
= optionGroupsCache
.length
;
14600 groupIndex
< groupLength
;
14602 // list of options for that group. (first item has the parent)
14603 optionGroup
= optionGroupsCache
[groupIndex
];
14605 for(index
= 1, length
= optionGroup
.length
; index
< length
; index
++) {
14606 if ((optionElement
= optionGroup
[index
].element
)[0].selected
) {
14607 key
= optionElement
.val();
14608 if (keyName
) locals
[keyName
] = key
;
14609 locals
[valueName
] = collection
[key
];
14610 value
.push(valueFn(scope
, locals
));
14615 key
= selectElement
.val();
14618 } else if (key
== ''){
14621 locals
[valueName
] = collection
[key
];
14622 if (keyName
) locals
[keyName
] = key
;
14623 value
= valueFn(scope
, locals
);
14626 ctrl
.$setViewValue(value
);
14630 ctrl
.$render
= render
;
14632 // TODO(vojta): can't we optimize this ?
14633 scope
.$watch(render
);
14635 function render() {
14636 var optionGroups
= {'':[]}, // Temporary location for the option groups before we render them
14637 optionGroupNames
= [''],
14641 existingParent
, existingOptions
, existingOption
,
14642 modelValue
= ctrl
.$modelValue
,
14643 values
= valuesFn(scope
) || [],
14644 keys
= keyName
? sortedKeys(values
) : values
,
14645 groupLength
, length
,
14649 selectedSet
= false, // nothing is selected yet
14655 selectedSet
= new HashMap(modelValue
);
14658 // We now build up the list of options we need (we merge later)
14659 for (index
= 0; length
= keys
.length
, index
< length
; index
++) {
14660 locals
[valueName
] = values
[keyName
? locals
[keyName
]=keys
[index
]:index
];
14661 optionGroupName
= groupByFn(scope
, locals
) || '';
14662 if (!(optionGroup
= optionGroups
[optionGroupName
])) {
14663 optionGroup
= optionGroups
[optionGroupName
] = [];
14664 optionGroupNames
.push(optionGroupName
);
14667 selected
= selectedSet
.remove(valueFn(scope
, locals
)) != undefined;
14669 selected
= modelValue
=== valueFn(scope
, locals
);
14670 selectedSet
= selectedSet
|| selected
; // see if at least one item is selected
14672 label
= displayFn(scope
, locals
); // what will be seen by the user
14673 label
= label
=== undefined ? '' : label
; // doing displayFn(scope, locals) || '' overwrites zero values
14675 id: keyName
? keys
[index
] : index
, // either the index into array or key from object
14677 selected: selected
// determine if we should be selected
14681 if (nullOption
|| modelValue
=== null) {
14682 // insert null option if we have a placeholder, or the model is null
14683 optionGroups
[''].unshift({id:'', label:'', selected:!selectedSet
});
14684 } else if (!selectedSet
) {
14685 // option could not be found, we have to insert the undefined item
14686 optionGroups
[''].unshift({id:'?', label:'', selected:true});
14690 // Now we need to update the list of DOM nodes to match the optionGroups we computed above
14691 for (groupIndex
= 0, groupLength
= optionGroupNames
.length
;
14692 groupIndex
< groupLength
;
14694 // current option group name or '' if no group
14695 optionGroupName
= optionGroupNames
[groupIndex
];
14697 // list of options for that group. (first item has the parent)
14698 optionGroup
= optionGroups
[optionGroupName
];
14700 if (optionGroupsCache
.length
<= groupIndex
) {
14701 // we need to grow the optionGroups
14703 element: optGroupTemplate
.clone().attr('label', optionGroupName
),
14704 label: optionGroup
.label
14706 existingOptions
= [existingParent
];
14707 optionGroupsCache
.push(existingOptions
);
14708 selectElement
.append(existingParent
.element
);
14710 existingOptions
= optionGroupsCache
[groupIndex
];
14711 existingParent
= existingOptions
[0]; // either SELECT (no group) or OPTGROUP element
14713 // update the OPTGROUP label if not the same.
14714 if (existingParent
.label
!= optionGroupName
) {
14715 existingParent
.element
.attr('label', existingParent
.label
= optionGroupName
);
14719 lastElement
= null; // start at the beginning
14720 for(index
= 0, length
= optionGroup
.length
; index
< length
; index
++) {
14721 option
= optionGroup
[index
];
14722 if ((existingOption
= existingOptions
[index
+1])) {
14724 lastElement
= existingOption
.element
;
14725 if (existingOption
.label
!== option
.label
) {
14726 lastElement
.text(existingOption
.label
= option
.label
);
14728 if (existingOption
.id
!== option
.id
) {
14729 lastElement
.val(existingOption
.id
= option
.id
);
14731 // lastElement.prop('selected') provided by jQuery has side-effects
14732 if (lastElement
[0].selected
!== option
.selected
) {
14733 lastElement
.prop('selected', (existingOption
.selected
= option
.selected
));
14738 // if it's a null option
14739 if (option
.id
=== '' && nullOption
) {
14740 // put back the pre-compiled element
14741 element
= nullOption
;
14743 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
14744 // in this version of jQuery on some browser the .text() returns a string
14745 // rather then the element.
14746 (element
= optionTemplate
.clone())
14748 .attr('selected', option
.selected
)
14749 .text(option
.label
);
14752 existingOptions
.push(existingOption
= {
14754 label: option
.label
,
14756 selected: option
.selected
14759 lastElement
.after(element
);
14761 existingParent
.element
.append(element
);
14763 lastElement
= element
;
14766 // remove any excessive OPTIONs in a group
14767 index
++; // increment since the existingOptions[0] is parent element not OPTION
14768 while(existingOptions
.length
> index
) {
14769 existingOptions
.pop().element
.remove();
14772 // remove any excessive OPTGROUPs from select
14773 while(optionGroupsCache
.length
> groupIndex
) {
14774 optionGroupsCache
.pop()[0].element
.remove();
14782 var optionDirective
= ['$interpolate', function($interpolate
) {
14783 var nullSelectCtrl
= {
14791 compile: function(element
, attr
) {
14792 if (isUndefined(attr
.value
)) {
14793 var interpolateFn
= $interpolate(element
.text(), true);
14794 if (!interpolateFn
) {
14795 attr
.$set('value', element
.text());
14799 return function (scope
, element
, attr
) {
14800 var selectCtrlName
= '$selectController',
14801 parent
= element
.parent(),
14802 selectCtrl
= parent
.data(selectCtrlName
) ||
14803 parent
.parent().data(selectCtrlName
); // in case we are in optgroup
14805 if (selectCtrl
&& selectCtrl
.databound
) {
14806 // For some reason Opera defaults to true and if not overridden this messes up the repeater.
14807 // We don't want the view to drive the initialization of the model anyway.
14808 element
.prop('selected', false);
14810 selectCtrl
= nullSelectCtrl
;
14813 if (interpolateFn
) {
14814 scope
.$watch(interpolateFn
, function interpolateWatchAction(newVal
, oldVal
) {
14815 attr
.$set('value', newVal
);
14816 if (newVal
!== oldVal
) selectCtrl
.removeOption(oldVal
);
14817 selectCtrl
.addOption(newVal
);
14820 selectCtrl
.addOption(attr
.value
);
14823 element
.bind('$destroy', function() {
14824 selectCtrl
.removeOption(attr
.value
);
14831 var styleDirective
= valueFn({
14836 //try to bind to jquery now so that one can write angular.element().read()
14837 //but we will rebind on bootstrap again.
14840 publishExternalAPI(angular
);
14842 jqLite(document
).ready(function() {
14843 angularInit(document
, bootstrap
);
14846 })(window
, document
);
14847 angular
.element(document
).find('head').append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');