Start writing a new settings page based on hoplite.
[bugdar.git] / static / angularjs-1.0.7.js
1 /**
2 * @license AngularJS v1.0.7
3 * (c) 2010-2012 Google, Inc. http://angularjs.org
4 * License: MIT
5 */
6 (function(window, document, undefined) {
7 'use strict';
8
9 ////////////////////////////////////
10
11 /**
12 * @ngdoc function
13 * @name angular.lowercase
14 * @function
15 *
16 * @description Converts the specified string to lowercase.
17 * @param {string} string String to be converted to lowercase.
18 * @returns {string} Lowercased string.
19 */
20 var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
21
22
23 /**
24 * @ngdoc function
25 * @name angular.uppercase
26 * @function
27 *
28 * @description Converts the specified string to uppercase.
29 * @param {string} string String to be converted to uppercase.
30 * @returns {string} Uppercased string.
31 */
32 var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
33
34
35 var manualLowercase = function(s) {
36 return isString(s)
37 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
38 : s;
39 };
40 var manualUppercase = function(s) {
41 return isString(s)
42 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
43 : s;
44 };
45
46
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;
53 }
54
55
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
60 slice = [].slice,
61 push = [].push,
62 toString = Object.prototype.toString,
63
64 /** @name angular */
65 angular = window.angular || (window.angular = {}),
66 angularModule,
67 nodeName_,
68 uid = ['0', '0', '0'];
69
70
71 /**
72 * @private
73 * @param {*} obj
74 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
75 */
76 function isArrayLike(obj) {
77 if (!obj || (typeof obj.length !== 'number')) return false;
78
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;
83 return true;
84 } else {
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)
89 }
90 }
91
92
93 /**
94 * @ngdoc function
95 * @name angular.forEach
96 * @function
97 *
98 * @description
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.
103 *
104 * Note: this function was previously known as `angular.foreach`.
105 *
106 <pre>
107 var values = {name: 'misko', gender: 'male'};
108 var log = [];
109 angular.forEach(values, function(value, key){
110 this.push(key + ': ' + value);
111 }, log);
112 expect(log).toEqual(['name: misko', 'gender:male']);
113 </pre>
114 *
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`.
119 */
120 function forEach(obj, iterator, context) {
121 var key;
122 if (obj) {
123 if (isFunction(obj)){
124 for (key in obj) {
125 if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
126 iterator.call(context, obj[key], key);
127 }
128 }
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);
134 } else {
135 for (key in obj) {
136 if (obj.hasOwnProperty(key)) {
137 iterator.call(context, obj[key], key);
138 }
139 }
140 }
141 }
142 return obj;
143 }
144
145 function sortedKeys(obj) {
146 var keys = [];
147 for (var key in obj) {
148 if (obj.hasOwnProperty(key)) {
149 keys.push(key);
150 }
151 }
152 return keys.sort();
153 }
154
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]);
159 }
160 return keys;
161 }
162
163
164 /**
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)}
168 */
169 function reverseParams(iteratorFn) {
170 return function(value, key) { iteratorFn(key, value) };
171 }
172
173 /**
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.
178 *
179 * @returns an unique alpha-numeric string
180 */
181 function nextUid() {
182 var index = uid.length;
183 var digit;
184
185 while(index) {
186 index--;
187 digit = uid[index].charCodeAt(0);
188 if (digit == 57 /*'9'*/) {
189 uid[index] = 'A';
190 return uid.join('');
191 }
192 if (digit == 90 /*'Z'*/) {
193 uid[index] = '0';
194 } else {
195 uid[index] = String.fromCharCode(digit + 1);
196 return uid.join('');
197 }
198 }
199 uid.unshift('0');
200 return uid.join('');
201 }
202
203
204 /**
205 * Set or clear the hashkey for an object.
206 * @param obj object
207 * @param h the hashkey (!truthy to delete the hashkey)
208 */
209 function setHashKey(obj, h) {
210 if (h) {
211 obj.$$hashKey = h;
212 }
213 else {
214 delete obj.$$hashKey;
215 }
216 }
217
218 /**
219 * @ngdoc function
220 * @name angular.extend
221 * @function
222 *
223 * @description
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.
226 *
227 * @param {Object} dst Destination object.
228 * @param {...Object} src Source object(s).
229 * @returns {Object} Reference to `dst`.
230 */
231 function extend(dst) {
232 var h = dst.$$hashKey;
233 forEach(arguments, function(obj){
234 if (obj !== dst) {
235 forEach(obj, function(value, key){
236 dst[key] = value;
237 });
238 }
239 });
240
241 setHashKey(dst,h);
242 return dst;
243 }
244
245 function int(str) {
246 return parseInt(str, 10);
247 }
248
249
250 function inherit(parent, extra) {
251 return extend(new (extend(function() {}, {prototype:parent}))(), extra);
252 }
253
254
255 /**
256 * @ngdoc function
257 * @name angular.noop
258 * @function
259 *
260 * @description
261 * A function that performs no operations. This function can be useful when writing code in the
262 * functional style.
263 <pre>
264 function foo(callback) {
265 var result = calculateResult();
266 (callback || angular.noop)(result);
267 }
268 </pre>
269 */
270 function noop() {}
271 noop.$inject = [];
272
273
274 /**
275 * @ngdoc function
276 * @name angular.identity
277 * @function
278 *
279 * @description
280 * A function that returns its first argument. This function is useful when writing code in the
281 * functional style.
282 *
283 <pre>
284 function transformer(transformationFn, value) {
285 return (transformationFn || identity)(value);
286 };
287 </pre>
288 */
289 function identity($) {return $;}
290 identity.$inject = [];
291
292
293 function valueFn(value) {return function() {return value;};}
294
295 /**
296 * @ngdoc function
297 * @name angular.isUndefined
298 * @function
299 *
300 * @description
301 * Determines if a reference is undefined.
302 *
303 * @param {*} value Reference to check.
304 * @returns {boolean} True if `value` is undefined.
305 */
306 function isUndefined(value){return typeof value == 'undefined';}
307
308
309 /**
310 * @ngdoc function
311 * @name angular.isDefined
312 * @function
313 *
314 * @description
315 * Determines if a reference is defined.
316 *
317 * @param {*} value Reference to check.
318 * @returns {boolean} True if `value` is defined.
319 */
320 function isDefined(value){return typeof value != 'undefined';}
321
322
323 /**
324 * @ngdoc function
325 * @name angular.isObject
326 * @function
327 *
328 * @description
329 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
330 * considered to be objects.
331 *
332 * @param {*} value Reference to check.
333 * @returns {boolean} True if `value` is an `Object` but not `null`.
334 */
335 function isObject(value){return value != null && typeof value == 'object';}
336
337
338 /**
339 * @ngdoc function
340 * @name angular.isString
341 * @function
342 *
343 * @description
344 * Determines if a reference is a `String`.
345 *
346 * @param {*} value Reference to check.
347 * @returns {boolean} True if `value` is a `String`.
348 */
349 function isString(value){return typeof value == 'string';}
350
351
352 /**
353 * @ngdoc function
354 * @name angular.isNumber
355 * @function
356 *
357 * @description
358 * Determines if a reference is a `Number`.
359 *
360 * @param {*} value Reference to check.
361 * @returns {boolean} True if `value` is a `Number`.
362 */
363 function isNumber(value){return typeof value == 'number';}
364
365
366 /**
367 * @ngdoc function
368 * @name angular.isDate
369 * @function
370 *
371 * @description
372 * Determines if a value is a date.
373 *
374 * @param {*} value Reference to check.
375 * @returns {boolean} True if `value` is a `Date`.
376 */
377 function isDate(value){
378 return toString.apply(value) == '[object Date]';
379 }
380
381
382 /**
383 * @ngdoc function
384 * @name angular.isArray
385 * @function
386 *
387 * @description
388 * Determines if a reference is an `Array`.
389 *
390 * @param {*} value Reference to check.
391 * @returns {boolean} True if `value` is an `Array`.
392 */
393 function isArray(value) {
394 return toString.apply(value) == '[object Array]';
395 }
396
397
398 /**
399 * @ngdoc function
400 * @name angular.isFunction
401 * @function
402 *
403 * @description
404 * Determines if a reference is a `Function`.
405 *
406 * @param {*} value Reference to check.
407 * @returns {boolean} True if `value` is a `Function`.
408 */
409 function isFunction(value){return typeof value == 'function';}
410
411
412 /**
413 * Checks if `obj` is a window object.
414 *
415 * @private
416 * @param {*} obj Object to check
417 * @returns {boolean} True if `obj` is a window obj.
418 */
419 function isWindow(obj) {
420 return obj && obj.document && obj.location && obj.alert && obj.setInterval;
421 }
422
423
424 function isScope(obj) {
425 return obj && obj.$evalAsync && obj.$watch;
426 }
427
428
429 function isFile(obj) {
430 return toString.apply(obj) === '[object File]';
431 }
432
433
434 function isBoolean(value) {
435 return typeof value == 'boolean';
436 }
437
438
439 function trim(value) {
440 return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
441 }
442
443 /**
444 * @ngdoc function
445 * @name angular.isElement
446 * @function
447 *
448 * @description
449 * Determines if a reference is a DOM element (or wrapped jQuery element).
450 *
451 * @param {*} value Reference to check.
452 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
453 */
454 function isElement(node) {
455 return 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
458 }
459
460 /**
461 * @param str 'key1,key2,...'
462 * @returns {object} in the form of {key1:true, key2:true, ...}
463 */
464 function makeMap(str){
465 var obj = {}, items = str.split(","), i;
466 for ( i = 0; i < items.length; i++ )
467 obj[ items[i] ] = true;
468 return obj;
469 }
470
471
472 if (msie < 9) {
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;
477 };
478 } else {
479 nodeName_ = function(element) {
480 return element.nodeName ? element.nodeName : element[0].nodeName;
481 };
482 }
483
484
485 function map(obj, iterator, context) {
486 var results = [];
487 forEach(obj, function(value, index, list) {
488 results.push(iterator.call(context, value, index, list));
489 });
490 return results;
491 }
492
493
494 /**
495 * @description
496 * Determines the number of elements in an array, the number of properties an object has, or
497 * the length of a string.
498 *
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.
501 *
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.
505 */
506 function size(obj, ownPropsOnly) {
507 var size = 0, key;
508
509 if (isArray(obj) || isString(obj)) {
510 return obj.length;
511 } else if (isObject(obj)){
512 for (key in obj)
513 if (!ownPropsOnly || obj.hasOwnProperty(key))
514 size++;
515 }
516
517 return size;
518 }
519
520
521 function includes(array, obj) {
522 return indexOf(array, obj) != -1;
523 }
524
525 function indexOf(array, obj) {
526 if (array.indexOf) return array.indexOf(obj);
527
528 for ( var i = 0; i < array.length; i++) {
529 if (obj === array[i]) return i;
530 }
531 return -1;
532 }
533
534 function arrayRemove(array, value) {
535 var index = indexOf(array, value);
536 if (index >=0)
537 array.splice(index, 1);
538 return value;
539 }
540
541 function isLeafNode (node) {
542 if (node) {
543 switch (node.nodeName) {
544 case "OPTION":
545 case "PRE":
546 case "TITLE":
547 return true;
548 }
549 }
550 return false;
551 }
552
553 /**
554 * @ngdoc function
555 * @name angular.copy
556 * @function
557 *
558 * @description
559 * Creates a deep copy of `source`, which should be an object or an array.
560 *
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.
565 *
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.
568 *
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.
574 */
575 function copy(source, destination){
576 if (isWindow(source) || isScope(source)) throw Error("Can't copy Window or Scope");
577 if (!destination) {
578 destination = source;
579 if (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, {});
586 }
587 }
588 } else {
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]));
594 }
595 } else {
596 var h = destination.$$hashKey;
597 forEach(destination, function(value, key){
598 delete destination[key];
599 });
600 for ( var key in source) {
601 destination[key] = copy(source[key]);
602 }
603 setHashKey(destination,h);
604 }
605 }
606 return destination;
607 }
608
609 /**
610 * Create a shallow copy of an object
611 */
612 function shallowCopy(src, dst) {
613 dst = dst || {};
614
615 for(var key in src) {
616 if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
617 dst[key] = src[key];
618 }
619 }
620
621 return dst;
622 }
623
624
625 /**
626 * @ngdoc function
627 * @name angular.equals
628 * @function
629 *
630 * @description
631 * Determines if two objects or two values are equivalent. Supports value types, arrays and
632 * objects.
633 *
634 * Two objects or values are considered equivalent if at least one of the following is true:
635 *
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)
639 *
640 * During a property comparision, properties of `function` type and properties with names
641 * that begin with `$` are ignored.
642 *
643 * Scope and DOMWindow objects are being compared only by identify (`===`).
644 *
645 * @param {*} o1 Object or value to compare.
646 * @param {*} o2 Object or value to compare.
647 * @returns {boolean} True if arguments are equal.
648 */
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;
654 if (t1 == t2) {
655 if (t1 == 'object') {
656 if (isArray(o1)) {
657 if ((length = o1.length) == o2.length) {
658 for(key=0; key<length; key++) {
659 if (!equals(o1[key], o2[key])) return false;
660 }
661 return true;
662 }
663 } else if (isDate(o1)) {
664 return isDate(o2) && o1.getTime() == o2.getTime();
665 } else {
666 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2)) return false;
667 keySet = {};
668 for(key in o1) {
669 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
670 if (!equals(o1[key], o2[key])) return false;
671 keySet[key] = true;
672 }
673 for(key in o2) {
674 if (!keySet[key] &&
675 key.charAt(0) !== '$' &&
676 o2[key] !== undefined &&
677 !isFunction(o2[key])) return false;
678 }
679 return true;
680 }
681 }
682 }
683 return false;
684 }
685
686
687 function concat(array1, array2, index) {
688 return array1.concat(slice.call(array2, index));
689 }
690
691 function sliceArgs(args, startIndex) {
692 return slice.call(args, startIndex || 0);
693 }
694
695
696 /**
697 * @ngdoc function
698 * @name angular.bind
699 * @function
700 *
701 * @description
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).
705 *
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.
710 */
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
715 ? function() {
716 return arguments.length
717 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
718 : fn.apply(self, curryArgs);
719 }
720 : function() {
721 return arguments.length
722 ? fn.apply(self, arguments)
723 : fn.call(self);
724 };
725 } else {
726 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
727 return fn;
728 }
729 }
730
731
732 function toJsonReplacer(key, value) {
733 var val = value;
734
735 if (/^\$+/.test(key)) {
736 val = undefined;
737 } else if (isWindow(value)) {
738 val = '$WINDOW';
739 } else if (value && document === value) {
740 val = '$DOCUMENT';
741 } else if (isScope(value)) {
742 val = '$SCOPE';
743 }
744
745 return val;
746 }
747
748
749 /**
750 * @ngdoc function
751 * @name angular.toJson
752 * @function
753 *
754 * @description
755 * Serializes input into a JSON-formatted string.
756 *
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`.
760 */
761 function toJson(obj, pretty) {
762 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
763 }
764
765
766 /**
767 * @ngdoc function
768 * @name angular.fromJson
769 * @function
770 *
771 * @description
772 * Deserializes a JSON string.
773 *
774 * @param {string} json JSON string to deserialize.
775 * @returns {Object|Array|Date|string|number} Deserialized thingy.
776 */
777 function fromJson(json) {
778 return isString(json)
779 ? JSON.parse(json)
780 : json;
781 }
782
783
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 == '[]');
788 } else {
789 value = false;
790 }
791 return value;
792 }
793
794 /**
795 * @returns {string} Returns the string representation of the element.
796 */
797 function startingTag(element) {
798 element = jqLite(element).clone();
799 try {
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.
802 element.html('');
803 } catch(e) {}
804 // As Per DOM Standards
805 var TEXT_NODE = 3;
806 var elemHtml = jqLite('<div>').append(element).html();
807 try {
808 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
809 elemHtml.
810 match(/^(<[^>]+>)/)[1].
811 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
812 } catch(e) {
813 return lowercase(elemHtml);
814 }
815
816 }
817
818
819 /////////////////////////////////////////////////
820
821 /**
822 * Parses an escaped url query string into key-value pairs.
823 * @returns Object.<(string|boolean)>
824 */
825 function parseKeyValue(/**string*/keyValue) {
826 var obj = {}, key_value, key;
827 forEach((keyValue || "").split('&'), function(keyValue){
828 if (keyValue) {
829 key_value = keyValue.split('=');
830 key = decodeURIComponent(key_value[0]);
831 obj[key] = isDefined(key_value[1]) ? decodeURIComponent(key_value[1]) : true;
832 }
833 });
834 return obj;
835 }
836
837 function toKeyValue(obj) {
838 var parts = [];
839 forEach(obj, function(value, key) {
840 parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true)));
841 });
842 return parts.length ? parts.join('&') : '';
843 }
844
845
846 /**
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
849 * segments:
850 * segment = *pchar
851 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
852 * pct-encoded = "%" HEXDIG HEXDIG
853 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
854 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
855 * / "*" / "+" / "," / ";" / "="
856 */
857 function encodeUriSegment(val) {
858 return encodeUriQuery(val, true).
859 replace(/%26/gi, '&').
860 replace(/%3D/gi, '=').
861 replace(/%2B/gi, '+');
862 }
863
864
865 /**
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 * / "*" / "+" / "," / ";" / "="
875 */
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' : '+'));
883 }
884
885
886 /**
887 * @ngdoc directive
888 * @name ng.directive:ngApp
889 *
890 * @element ANY
891 * @param {angular.Module} ngApp an optional application
892 * {@link angular.module module} name to load.
893 *
894 * @description
895 *
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.
900 *
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`.
904 *
905 * `ngApp` is the easiest way to bootstrap an application.
906 *
907 <doc:example>
908 <doc:source>
909 I can add: 1 + 2 = {{ 1+2 }}
910 </doc:source>
911 </doc:example>
912 *
913 */
914 function angularInit(element, bootstrap) {
915 var elements = [element],
916 appElement,
917 module,
918 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
919 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
920
921 function append(element) {
922 element && elements.push(element);
923 }
924
925 forEach(names, function(name) {
926 names[name] = true;
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);
933 }
934 });
935
936 forEach(elements, function(element) {
937 if (!appElement) {
938 var className = ' ' + element.className + ' ';
939 var match = NG_APP_CLASS_REGEXP.exec(className);
940 if (match) {
941 appElement = element;
942 module = (match[2] || '').replace(/\s+/g, ',');
943 } else {
944 forEach(element.attributes, function(attr) {
945 if (!appElement && names[attr.name]) {
946 appElement = element;
947 module = attr.value;
948 }
949 });
950 }
951 }
952 });
953 if (appElement) {
954 bootstrap(appElement, module ? [module] : []);
955 }
956 }
957
958 /**
959 * @ngdoc function
960 * @name angular.bootstrap
961 * @description
962 * Use this function to manually start up angular application.
963 *
964 * See: {@link guide/bootstrap Bootstrap}
965 *
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.
969 */
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);
976 }]);
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);
984 });
985 }]
986 );
987 return injector;
988 };
989
990 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
991
992 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
993 return resumeBootstrapInternal();
994 }
995
996 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
997 angular.resumeBootstrap = function(extraModules) {
998 forEach(extraModules, function(module) {
999 modules.push(module);
1000 });
1001 resumeBootstrapInternal();
1002 };
1003 }
1004
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();
1010 });
1011 }
1012
1013 function bindJQuery() {
1014 // bind to jQuery if present;
1015 jQuery = window.jQuery;
1016 // reset to jQuery or default to us.
1017 if (jQuery) {
1018 jqLite = jQuery;
1019 extend(jQuery.fn, {
1020 scope: JQLitePrototype.scope,
1021 controller: JQLitePrototype.controller,
1022 injector: JQLitePrototype.injector,
1023 inheritedData: JQLitePrototype.inheritedData
1024 });
1025 JQLitePatchJQueryRemove('remove', true);
1026 JQLitePatchJQueryRemove('empty');
1027 JQLitePatchJQueryRemove('html');
1028 } else {
1029 jqLite = JQLite;
1030 }
1031 angular.element = jqLite;
1032 }
1033
1034 /**
1035 * throw error if the argument is falsy.
1036 */
1037 function assertArg(arg, name, reason) {
1038 if (!arg) {
1039 throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
1040 }
1041 return arg;
1042 }
1043
1044 function assertArgFn(arg, name, acceptArrayAnnotation) {
1045 if (acceptArrayAnnotation && isArray(arg)) {
1046 arg = arg[arg.length - 1];
1047 }
1048
1049 assertArg(isFunction(arg), name, 'not a function, got ' +
1050 (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
1051 return arg;
1052 }
1053
1054 /**
1055 * @ngdoc interface
1056 * @name angular.Module
1057 * @description
1058 *
1059 * Interface for configuring angular {@link angular.module modules}.
1060 */
1061
1062 function setupModuleLoader(window) {
1063
1064 function ensure(obj, name, factory) {
1065 return obj[name] || (obj[name] = factory());
1066 }
1067
1068 return ensure(ensure(window, 'angular', Object), 'module', function() {
1069 /** @type {Object.<string, angular.Module>} */
1070 var modules = {};
1071
1072 /**
1073 * @ngdoc function
1074 * @name angular.module
1075 * @description
1076 *
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.
1080 *
1081 *
1082 * # Module
1083 *
1084 * A module is a collocation of services, directives, filters, and configuration information. Module
1085 * is used to configure the {@link AUTO.$injector $injector}.
1086 *
1087 * <pre>
1088 * // Create a new module
1089 * var myModule = angular.module('myModule', []);
1090 *
1091 * // register a new service
1092 * myModule.value('appName', 'MyCoolApp');
1093 *
1094 * // configure existing services inside initialization blocks.
1095 * myModule.config(function($locationProvider) {
1096 * // Configure existing providers
1097 * $locationProvider.hashPrefix('!');
1098 * });
1099 * </pre>
1100 *
1101 * Then you can create an injector and load your modules like this:
1102 *
1103 * <pre>
1104 * var injector = angular.injector(['ng', 'MyModule'])
1105 * </pre>
1106 *
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.
1110 *
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.
1117 */
1118 return function module(name, requires, configFn) {
1119 if (requires && modules.hasOwnProperty(name)) {
1120 modules[name] = null;
1121 }
1122 return ensure(modules, name, function() {
1123 if (!requires) {
1124 throw Error('No module: ' + name);
1125 }
1126
1127 /** @type {!Array.<Array.<*>>} */
1128 var invokeQueue = [];
1129
1130 /** @type {!Array.<Function>} */
1131 var runBlocks = [];
1132
1133 var config = invokeLater('$injector', 'invoke');
1134
1135 /** @type {angular.Module} */
1136 var moduleInstance = {
1137 // Private state
1138 _invokeQueue: invokeQueue,
1139 _runBlocks: runBlocks,
1140
1141 /**
1142 * @ngdoc property
1143 * @name angular.Module#requires
1144 * @propertyOf angular.Module
1145 * @returns {Array.<string>} List of module names which must be loaded before this module.
1146 * @description
1147 * Holds the list of modules which the injector will load before the current module is loaded.
1148 */
1149 requires: requires,
1150
1151 /**
1152 * @ngdoc property
1153 * @name angular.Module#name
1154 * @propertyOf angular.Module
1155 * @returns {string} Name of the module.
1156 * @description
1157 */
1158 name: name,
1159
1160
1161 /**
1162 * @ngdoc method
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.
1167 * @description
1168 * See {@link AUTO.$provide#provider $provide.provider()}.
1169 */
1170 provider: invokeLater('$provide', 'provider'),
1171
1172 /**
1173 * @ngdoc method
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.
1178 * @description
1179 * See {@link AUTO.$provide#factory $provide.factory()}.
1180 */
1181 factory: invokeLater('$provide', 'factory'),
1182
1183 /**
1184 * @ngdoc method
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.
1189 * @description
1190 * See {@link AUTO.$provide#service $provide.service()}.
1191 */
1192 service: invokeLater('$provide', 'service'),
1193
1194 /**
1195 * @ngdoc method
1196 * @name angular.Module#value
1197 * @methodOf angular.Module
1198 * @param {string} name service name
1199 * @param {*} object Service instance object.
1200 * @description
1201 * See {@link AUTO.$provide#value $provide.value()}.
1202 */
1203 value: invokeLater('$provide', 'value'),
1204
1205 /**
1206 * @ngdoc method
1207 * @name angular.Module#constant
1208 * @methodOf angular.Module
1209 * @param {string} name constant name
1210 * @param {*} object Constant value.
1211 * @description
1212 * Because the constant are fixed, they get applied before other provide methods.
1213 * See {@link AUTO.$provide#constant $provide.constant()}.
1214 */
1215 constant: invokeLater('$provide', 'constant', 'unshift'),
1216
1217 /**
1218 * @ngdoc method
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.
1223 * @description
1224 * See {@link ng.$filterProvider#register $filterProvider.register()}.
1225 */
1226 filter: invokeLater('$filterProvider', 'register'),
1227
1228 /**
1229 * @ngdoc method
1230 * @name angular.Module#controller
1231 * @methodOf angular.Module
1232 * @param {string} name Controller name.
1233 * @param {Function} constructor Controller constructor function.
1234 * @description
1235 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
1236 */
1237 controller: invokeLater('$controllerProvider', 'register'),
1238
1239 /**
1240 * @ngdoc method
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
1245 * directives.
1246 * @description
1247 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
1248 */
1249 directive: invokeLater('$compileProvider', 'directive'),
1250
1251 /**
1252 * @ngdoc method
1253 * @name angular.Module#config
1254 * @methodOf angular.Module
1255 * @param {Function} configFn Execute this function on module load. Useful for service
1256 * configuration.
1257 * @description
1258 * Use this method to register work which needs to be performed on module loading.
1259 */
1260 config: config,
1261
1262 /**
1263 * @ngdoc method
1264 * @name angular.Module#run
1265 * @methodOf angular.Module
1266 * @param {Function} initializationFn Execute this function after injector creation.
1267 * Useful for application initialization.
1268 * @description
1269 * Use this method to register work which should be performed when the injector is done
1270 * loading all modules.
1271 */
1272 run: function(block) {
1273 runBlocks.push(block);
1274 return this;
1275 }
1276 };
1277
1278 if (configFn) {
1279 config(configFn);
1280 }
1281
1282 return moduleInstance;
1283
1284 /**
1285 * @param {string} provider
1286 * @param {string} method
1287 * @param {String=} insertMethod
1288 * @returns {angular.Module}
1289 */
1290 function invokeLater(provider, method, insertMethod) {
1291 return function() {
1292 invokeQueue[insertMethod || 'push']([provider, method, arguments]);
1293 return moduleInstance;
1294 }
1295 }
1296 });
1297 };
1298 });
1299
1300 }
1301
1302 /**
1303 * @ngdoc property
1304 * @name angular.version
1305 * @description
1306 * An object that contains information about the current AngularJS version. This object has the
1307 * following properties:
1308 *
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".
1314 */
1315 var version = {
1316 full: '1.0.7', // all of these placeholder strings will be replaced by grunt's
1317 major: 1, // package task
1318 minor: 0,
1319 dot: 7,
1320 codeName: 'monochromatic-rainbow'
1321 };
1322
1323
1324 function publishExternalAPI(angular){
1325 extend(angular, {
1326 'bootstrap': bootstrap,
1327 'copy': copy,
1328 'extend': extend,
1329 'equals': equals,
1330 'element': jqLite,
1331 'forEach': forEach,
1332 'injector': createInjector,
1333 'noop':noop,
1334 'bind':bind,
1335 'toJson': toJson,
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,
1345 'isArray': isArray,
1346 'version': version,
1347 'isDate': isDate,
1348 'lowercase': lowercase,
1349 'uppercase': uppercase,
1350 'callbacks': {counter: 0}
1351 });
1352
1353 angularModule = setupModuleLoader(window);
1354 try {
1355 angularModule('ngLocale');
1356 } catch (e) {
1357 angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
1358 }
1359
1360 angularModule('ng', ['ngLocale'], ['$provide',
1361 function ngModule($provide) {
1362 $provide.provider('$compile', $CompileProvider).
1363 directive({
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
1403 }).
1404 directive(ngAttributeAliasDirectives).
1405 directive(ngEventDirectives);
1406 $provide.provider({
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,
1418 $log: $LogProvider,
1419 $parse: $ParseProvider,
1420 $route: $RouteProvider,
1421 $routeParams: $RouteParamsProvider,
1422 $rootScope: $RootScopeProvider,
1423 $q: $QProvider,
1424 $sniffer: $SnifferProvider,
1425 $templateCache: $TemplateCacheProvider,
1426 $timeout: $TimeoutProvider,
1427 $window: $WindowProvider
1428 });
1429 }
1430 ]);
1431 }
1432
1433 //////////////////////////////////
1434 //JQLite
1435 //////////////////////////////////
1436
1437 /**
1438 * @ngdoc function
1439 * @name angular.element
1440 * @function
1441 *
1442 * @description
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).
1447 *
1448 * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
1449 * event fired.
1450 *
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.
1455 *
1456 * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
1457 * raw DOM references.
1458 *
1459 * ## Angular's jQuery lite provides the following methods:
1460 *
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/)
1491 *
1492 * ## In addtion to the above, Angular provides additional methods to both jQuery and jQuery lite:
1493 *
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.
1497 * `'ngModel'`).
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.
1503 *
1504 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
1505 * @returns {Object} jQuery object.
1506 */
1507
1508 var jqCache = JQLite.cache = {},
1509 jqName = JQLite.expando = 'ng-' + new Date().getTime(),
1510 jqId = 1,
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); });
1517
1518 function jqNextId() { return ++jqId; }
1519
1520
1521 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
1522 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
1523
1524 /**
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
1528 */
1529 function camelCase(name) {
1530 return name.
1531 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
1532 return offset ? letter.toUpperCase() : letter;
1533 }).
1534 replace(MOZ_HACK_REGEXP, 'Moz$1');
1535 }
1536
1537 /////////////////////////////////////////////
1538 // jQuery mutation patch
1539 //
1540 // In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
1541 // $destroy event on all DOM nodes being removed.
1542 //
1543 /////////////////////////////////////////////
1544
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;
1550
1551 function removePatch() {
1552 var list = [this],
1553 fireEvent = dispatchThis,
1554 set, setIndex, setLength,
1555 element, childIndex, childLength, children,
1556 fns, events;
1557
1558 while(list.length) {
1559 set = list.shift();
1560 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
1561 element = jqLite(set[setIndex]);
1562 if (fireEvent) {
1563 element.triggerHandler('$destroy');
1564 } else {
1565 fireEvent = !fireEvent;
1566 }
1567 for(childIndex = 0, childLength = (children = element.children()).length;
1568 childIndex < childLength;
1569 childIndex++) {
1570 list.push(jQuery(children[childIndex]));
1571 }
1572 }
1573 }
1574 return originalJqFn.apply(this, arguments);
1575 }
1576 }
1577
1578 /////////////////////////////////////////////
1579 function JQLite(element) {
1580 if (element instanceof JQLite) {
1581 return element;
1582 }
1583 if (!(this instanceof JQLite)) {
1584 if (isString(element) && element.charAt(0) != '<') {
1585 throw Error('selectors not implemented');
1586 }
1587 return new JQLite(element);
1588 }
1589
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>&#160;</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.
1598 } else {
1599 JQLiteAddNodes(this, element);
1600 }
1601 }
1602
1603 function JQLiteClone(element) {
1604 return element.cloneNode(true);
1605 }
1606
1607 function JQLiteDealoc(element){
1608 JQLiteRemoveData(element);
1609 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
1610 JQLiteDealoc(children[i]);
1611 }
1612 }
1613
1614 function JQLiteUnbind(element, type, fn) {
1615 var events = JQLiteExpandoStore(element, 'events'),
1616 handle = JQLiteExpandoStore(element, 'handle');
1617
1618 if (!handle) return; //no listeners registered
1619
1620 if (isUndefined(type)) {
1621 forEach(events, function(eventHandler, type) {
1622 removeEventListenerFn(element, type, eventHandler);
1623 delete events[type];
1624 });
1625 } else {
1626 if (isUndefined(fn)) {
1627 removeEventListenerFn(element, type, events[type]);
1628 delete events[type];
1629 } else {
1630 arrayRemove(events[type], fn);
1631 }
1632 }
1633 }
1634
1635 function JQLiteRemoveData(element) {
1636 var expandoId = element[jqName],
1637 expandoStore = jqCache[expandoId];
1638
1639 if (expandoStore) {
1640 if (expandoStore.handle) {
1641 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
1642 JQLiteUnbind(element);
1643 }
1644 delete jqCache[expandoId];
1645 element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
1646 }
1647 }
1648
1649 function JQLiteExpandoStore(element, key, value) {
1650 var expandoId = element[jqName],
1651 expandoStore = jqCache[expandoId || -1];
1652
1653 if (isDefined(value)) {
1654 if (!expandoStore) {
1655 element[jqName] = expandoId = jqNextId();
1656 expandoStore = jqCache[expandoId] = {};
1657 }
1658 expandoStore[key] = value;
1659 } else {
1660 return expandoStore && expandoStore[key];
1661 }
1662 }
1663
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);
1669
1670 if (!data && !isSimpleGetter) {
1671 JQLiteExpandoStore(element, 'data', data = {});
1672 }
1673
1674 if (isSetter) {
1675 data[key] = value;
1676 } else {
1677 if (keyDefined) {
1678 if (isSimpleGetter) {
1679 // don't create data in this case.
1680 return data && data[key];
1681 } else {
1682 extend(data, key);
1683 }
1684 } else {
1685 return data;
1686 }
1687 }
1688 }
1689
1690 function JQLiteHasClass(element, selector) {
1691 return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
1692 indexOf( " " + selector + " " ) > -1);
1693 }
1694
1695 function JQLiteRemoveClass(element, cssClasses) {
1696 if (cssClasses) {
1697 forEach(cssClasses.split(' '), function(cssClass) {
1698 element.className = trim(
1699 (" " + element.className + " ")
1700 .replace(/[\n\t]/g, " ")
1701 .replace(" " + trim(cssClass) + " ", " ")
1702 );
1703 });
1704 }
1705 }
1706
1707 function JQLiteAddClass(element, cssClasses) {
1708 if (cssClasses) {
1709 forEach(cssClasses.split(' '), function(cssClass) {
1710 if (!JQLiteHasClass(element, cssClass)) {
1711 element.className = trim(element.className + ' ' + trim(cssClass));
1712 }
1713 });
1714 }
1715 }
1716
1717 function JQLiteAddNodes(root, elements) {
1718 if (elements) {
1719 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
1720 ? elements
1721 : [ elements ];
1722 for(var i=0; i < elements.length; i++) {
1723 root.push(elements[i]);
1724 }
1725 }
1726 }
1727
1728 function JQLiteController(element, name) {
1729 return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
1730 }
1731
1732 function JQLiteInheritedData(element, name, value) {
1733 element = jqLite(element);
1734
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');
1739 }
1740
1741 while (element.length) {
1742 if (value = element.data(name)) return value;
1743 element = element.parent();
1744 }
1745 }
1746
1747 //////////////////////////////////////////
1748 // Functions which are declared directly.
1749 //////////////////////////////////////////
1750 var JQLitePrototype = JQLite.prototype = {
1751 ready: function(fn) {
1752 var fired = false;
1753
1754 function trigger() {
1755 if (fired) return;
1756 fired = true;
1757 fn();
1758 }
1759
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
1763 },
1764 toString: function() {
1765 var value = [];
1766 forEach(this, function(e){ value.push('' + e);});
1767 return '[' + value.join(', ') + ']';
1768 },
1769
1770 eq: function(index) {
1771 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
1772 },
1773
1774 length: 0,
1775 push: push,
1776 sort: [].sort,
1777 splice: [].splice
1778 };
1779
1780 //////////////////////////////////////////
1781 // Functions iterating getter/setters.
1782 // these functions return self on setter and
1783 // value on get.
1784 //////////////////////////////////////////
1785 var BOOLEAN_ATTR = {};
1786 forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
1787 BOOLEAN_ATTR[lowercase(value)] = value;
1788 });
1789 var BOOLEAN_ELEMENTS = {};
1790 forEach('input,select,option,textarea,button,form'.split(','), function(value) {
1791 BOOLEAN_ELEMENTS[uppercase(value)] = true;
1792 });
1793
1794 function getBooleanAttrName(element, name) {
1795 // check dom last since we will most likely fail on name
1796 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
1797
1798 // booleanAttr is here twice to minimize DOM access
1799 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr;
1800 }
1801
1802 forEach({
1803 data: JQLiteData,
1804 inheritedData: JQLiteInheritedData,
1805
1806 scope: function(element) {
1807 return JQLiteInheritedData(element, '$scope');
1808 },
1809
1810 controller: JQLiteController ,
1811
1812 injector: function(element) {
1813 return JQLiteInheritedData(element, '$injector');
1814 },
1815
1816 removeAttr: function(element,name) {
1817 element.removeAttribute(name);
1818 },
1819
1820 hasClass: JQLiteHasClass,
1821
1822 css: function(element, name, value) {
1823 name = camelCase(name);
1824
1825 if (isDefined(value)) {
1826 element.style[name] = value;
1827 } else {
1828 var val;
1829
1830 if (msie <= 8) {
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';
1834 }
1835
1836 val = val || element.style[name];
1837
1838 if (msie <= 8) {
1839 // jquery weirdness :-/
1840 val = (val === '') ? undefined : val;
1841 }
1842
1843 return val;
1844 }
1845 },
1846
1847 attr: function(element, name, value){
1848 var lowercasedName = lowercase(name);
1849 if (BOOLEAN_ATTR[lowercasedName]) {
1850 if (isDefined(value)) {
1851 if (!!value) {
1852 element[name] = true;
1853 element.setAttribute(name, lowercasedName);
1854 } else {
1855 element[name] = false;
1856 element.removeAttribute(lowercasedName);
1857 }
1858 } else {
1859 return (element[name] ||
1860 (element.attributes.getNamedItem(name)|| noop).specified)
1861 ? lowercasedName
1862 : undefined;
1863 }
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;
1872 }
1873 },
1874
1875 prop: function(element, name, value) {
1876 if (isDefined(value)) {
1877 element[name] = value;
1878 } else {
1879 return element[name];
1880 }
1881 },
1882
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;
1889 } else {
1890 if (isUndefined(value))
1891 return element.nodeValue;
1892 element.nodeValue = value;
1893 }
1894 }
1895 : function(element, value) {
1896 if (isUndefined(value)) {
1897 return element.textContent;
1898 }
1899 element.textContent = value;
1900 }, {$dv:''}),
1901
1902 val: function(element, value) {
1903 if (isUndefined(value)) {
1904 return element.value;
1905 }
1906 element.value = value;
1907 },
1908
1909 html: function(element, value) {
1910 if (isUndefined(value)) {
1911 return element.innerHTML;
1912 }
1913 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
1914 JQLiteDealoc(childNodes[i]);
1915 }
1916 element.innerHTML = value;
1917 }
1918 }, function(fn, name){
1919 /**
1920 * Properties: writes return selection, reads return first value
1921 */
1922 JQLite.prototype[name] = function(arg1, arg2) {
1923 var i, key;
1924
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)) {
1929
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
1934 fn(this[i], arg1);
1935 } else {
1936 for (key in arg1) {
1937 fn(this[i], key, arg1[key]);
1938 }
1939 }
1940 }
1941 // return self for chaining
1942 return this;
1943 } else {
1944 // we are a read, so read the first child.
1945 if (this.length)
1946 return fn(this[0], arg1, arg2);
1947 }
1948 } else {
1949 // we are a write, so apply to all children
1950 for(i=0; i < this.length; i++) {
1951 fn(this[i], arg1, arg2);
1952 }
1953 // return self for chaining
1954 return this;
1955 }
1956 return fn.$dv;
1957 };
1958 });
1959
1960 function createEventHandler(element, events) {
1961 var eventHandler = function (event, type) {
1962 if (!event.preventDefault) {
1963 event.preventDefault = function() {
1964 event.returnValue = false; //ie
1965 };
1966 }
1967
1968 if (!event.stopPropagation) {
1969 event.stopPropagation = function() {
1970 event.cancelBubble = true; //ie
1971 };
1972 }
1973
1974 if (!event.target) {
1975 event.target = event.srcElement || document;
1976 }
1977
1978 if (isUndefined(event.defaultPrevented)) {
1979 var prevent = event.preventDefault;
1980 event.preventDefault = function() {
1981 event.defaultPrevented = true;
1982 prevent.call(event);
1983 };
1984 event.defaultPrevented = false;
1985 }
1986
1987 event.isDefaultPrevented = function() {
1988 return event.defaultPrevented;
1989 };
1990
1991 forEach(events[type || event.type], function(fn) {
1992 fn.call(element, event);
1993 });
1994
1995 // Remove monkey-patched methods (IE),
1996 // as they would cause memory leaks in IE8.
1997 if (msie <= 8) {
1998 // IE7/8 does not allow to delete property on native object
1999 event.preventDefault = null;
2000 event.stopPropagation = null;
2001 event.isDefaultPrevented = null;
2002 } else {
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;
2007 }
2008 };
2009 eventHandler.elem = element;
2010 return eventHandler;
2011 }
2012
2013 //////////////////////////////////////////
2014 // Functions iterating traversal.
2015 // These functions chain results into a single
2016 // selector.
2017 //////////////////////////////////////////
2018 forEach({
2019 removeData: JQLiteRemoveData,
2020
2021 dealoc: JQLiteDealoc,
2022
2023 bind: function bindFn(element, type, fn){
2024 var events = JQLiteExpandoStore(element, 'events'),
2025 handle = JQLiteExpandoStore(element, 'handle');
2026
2027 if (!events) JQLiteExpandoStore(element, 'events', events = {});
2028 if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
2029
2030 forEach(type.split(' '), function(type){
2031 var eventFns = events[type];
2032
2033 if (!eventFns) {
2034 if (type == 'mouseenter' || type == 'mouseleave') {
2035 var contains = document.body.contains || document.body.compareDocumentPosition ?
2036 function( a, b ) {
2037 var adown = a.nodeType === 9 ? a.documentElement : a,
2038 bup = b && b.parentNode;
2039 return a === bup || !!( bup && bup.nodeType === 1 && (
2040 adown.contains ?
2041 adown.contains( bup ) :
2042 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
2043 ));
2044 } :
2045 function( a, b ) {
2046 if ( b ) {
2047 while ( (b = b.parentNode) ) {
2048 if ( b === a ) {
2049 return true;
2050 }
2051 }
2052 }
2053 return false;
2054 };
2055
2056 events[type] = [];
2057
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);
2068 }
2069
2070 });
2071
2072 } else {
2073 addEventListenerFn(element, type, handle);
2074 events[type] = [];
2075 }
2076 eventFns = events[type]
2077 }
2078 eventFns.push(fn);
2079 });
2080 },
2081
2082 unbind: JQLiteUnbind,
2083
2084 replaceWith: function(element, replaceNode) {
2085 var index, parent = element.parentNode;
2086 JQLiteDealoc(element);
2087 forEach(new JQLite(replaceNode), function(node){
2088 if (index) {
2089 parent.insertBefore(node, index.nextSibling);
2090 } else {
2091 parent.replaceChild(node, element);
2092 }
2093 index = node;
2094 });
2095 },
2096
2097 children: function(element) {
2098 var children = [];
2099 forEach(element.childNodes, function(element){
2100 if (element.nodeType === 1)
2101 children.push(element);
2102 });
2103 return children;
2104 },
2105
2106 contents: function(element) {
2107 return element.childNodes || [];
2108 },
2109
2110 append: function(element, node) {
2111 forEach(new JQLite(node), function(child){
2112 if (element.nodeType === 1)
2113 element.appendChild(child);
2114 });
2115 },
2116
2117 prepend: function(element, node) {
2118 if (element.nodeType === 1) {
2119 var index = element.firstChild;
2120 forEach(new JQLite(node), function(child){
2121 if (index) {
2122 element.insertBefore(child, index);
2123 } else {
2124 element.appendChild(child);
2125 index = child;
2126 }
2127 });
2128 }
2129 },
2130
2131 wrap: function(element, wrapNode) {
2132 wrapNode = jqLite(wrapNode)[0];
2133 var parent = element.parentNode;
2134 if (parent) {
2135 parent.replaceChild(wrapNode, element);
2136 }
2137 wrapNode.appendChild(element);
2138 },
2139
2140 remove: function(element) {
2141 JQLiteDealoc(element);
2142 var parent = element.parentNode;
2143 if (parent) parent.removeChild(element);
2144 },
2145
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);
2150 index = node;
2151 });
2152 },
2153
2154 addClass: JQLiteAddClass,
2155 removeClass: JQLiteRemoveClass,
2156
2157 toggleClass: function(element, selector, condition) {
2158 if (isUndefined(condition)) {
2159 condition = !JQLiteHasClass(element, selector);
2160 }
2161 (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
2162 },
2163
2164 parent: function(element) {
2165 var parent = element.parentNode;
2166 return parent && parent.nodeType !== 11 ? parent : null;
2167 },
2168
2169 next: function(element) {
2170 if (element.nextElementSibling) {
2171 return element.nextElementSibling;
2172 }
2173
2174 // IE8 doesn't have nextElementSibling
2175 var elm = element.nextSibling;
2176 while (elm != null && elm.nodeType !== 1) {
2177 elm = elm.nextSibling;
2178 }
2179 return elm;
2180 },
2181
2182 find: function(element, selector) {
2183 return element.getElementsByTagName(selector);
2184 },
2185
2186 clone: JQLiteClone,
2187
2188 triggerHandler: function(element, eventName) {
2189 var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName];
2190
2191 forEach(eventFns, function(fn) {
2192 fn.call(element, null);
2193 });
2194 }
2195 }, function(fn, name){
2196 /**
2197 * chaining functions
2198 */
2199 JQLite.prototype[name] = function(arg1, arg2) {
2200 var value;
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);
2207 }
2208 } else {
2209 JQLiteAddNodes(value, fn(this[i], arg1, arg2));
2210 }
2211 }
2212 return value == undefined ? this : value;
2213 };
2214 });
2215
2216 /**
2217 * Computes a hash of an 'obj'.
2218 * Hash of a:
2219 * string is string
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.
2223 *
2224 * @param obj
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.
2227 */
2228 function hashKey(obj) {
2229 var objType = typeof obj,
2230 key;
2231
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();
2238 }
2239 } else {
2240 key = obj;
2241 }
2242
2243 return objType + ':' + key;
2244 }
2245
2246 /**
2247 * HashMap which can use objects as keys
2248 */
2249 function HashMap(array){
2250 forEach(array, this.put, this);
2251 }
2252 HashMap.prototype = {
2253 /**
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
2257 */
2258 put: function(key, value) {
2259 this[hashKey(key)] = value;
2260 },
2261
2262 /**
2263 * @param key
2264 * @returns the value for the key
2265 */
2266 get: function(key) {
2267 return this[hashKey(key)];
2268 },
2269
2270 /**
2271 * Remove the key/value pair
2272 * @param key
2273 */
2274 remove: function(key) {
2275 var value = this[key = hashKey(key)];
2276 delete this[key];
2277 return value;
2278 }
2279 };
2280
2281 /**
2282 * A map where multiple values can be added to the same key such that they form a queue.
2283 * @returns {HashQueueMap}
2284 */
2285 function HashQueueMap() {}
2286 HashQueueMap.prototype = {
2287 /**
2288 * Same as array push, but using an array as the value for the hash
2289 */
2290 push: function(key, value) {
2291 var array = this[key = hashKey(key)];
2292 if (!array) {
2293 this[key] = [value];
2294 } else {
2295 array.push(value);
2296 }
2297 },
2298
2299 /**
2300 * Same as array shift, but using an array as the value for the hash
2301 */
2302 shift: function(key) {
2303 var array = this[key = hashKey(key)];
2304 if (array) {
2305 if (array.length == 1) {
2306 delete this[key];
2307 return array[0];
2308 } else {
2309 return array.shift();
2310 }
2311 }
2312 },
2313
2314 /**
2315 * return the first item without deleting it
2316 */
2317 peek: function(key) {
2318 var array = this[hashKey(key)];
2319 if (array) {
2320 return array[0];
2321 }
2322 }
2323 };
2324
2325 /**
2326 * @ngdoc function
2327 * @name angular.injector
2328 * @function
2329 *
2330 * @description
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}).
2333 *
2334
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}.
2338 *
2339 * @example
2340 * Typical usage
2341 * <pre>
2342 * // create an injector
2343 * var $injector = angular.injector(['ng']);
2344 *
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();
2350 * });
2351 * </pre>
2352 */
2353
2354
2355 /**
2356 * @ngdoc overview
2357 * @name AUTO
2358 * @description
2359 *
2360 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
2361 */
2362
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) {
2368 var $inject,
2369 fnText,
2370 argDecl,
2371 last;
2372
2373 if (typeof fn == 'function') {
2374 if (!($inject = fn.$inject)) {
2375 $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){
2380 $inject.push(name);
2381 });
2382 });
2383 fn.$inject = $inject;
2384 }
2385 } else if (isArray(fn)) {
2386 last = fn.length - 1;
2387 assertArgFn(fn[last], 'fn');
2388 $inject = fn.slice(0, last);
2389 } else {
2390 assertArgFn(fn, 'fn', true);
2391 }
2392 return $inject;
2393 }
2394
2395 ///////////////////////////////////////
2396
2397 /**
2398 * @ngdoc object
2399 * @name AUTO.$injector
2400 * @function
2401 *
2402 * @description
2403 *
2404 * `$injector` is used to retrieve object instances as defined by
2405 * {@link AUTO.$provide provider}, instantiate types, invoke methods,
2406 * and load modules.
2407 *
2408 * The following always holds true:
2409 *
2410 * <pre>
2411 * var $injector = angular.injector();
2412 * expect($injector.get('$injector')).toBe($injector);
2413 * expect($injector.invoke(function($injector){
2414 * return $injector;
2415 * }).toBe($injector);
2416 * </pre>
2417 *
2418 * # Injection Function Annotation
2419 *
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.
2422 *
2423 * <pre>
2424 * // inferred (only works if code not minified/obfuscated)
2425 * $injector.invoke(function(serviceA){});
2426 *
2427 * // annotated
2428 * function explicit(serviceA) {};
2429 * explicit.$inject = ['serviceA'];
2430 * $injector.invoke(explicit);
2431 *
2432 * // inline
2433 * $injector.invoke(['serviceA', function(serviceA){}]);
2434 * </pre>
2435 *
2436 * ## Inference
2437 *
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.
2441 *
2442 * ## `$inject` Annotation
2443 * By adding a `$inject` property onto a function the injection parameters can be specified.
2444 *
2445 * ## Inline
2446 * As an array of injection names, where the last item in the array is the function to call.
2447 */
2448
2449 /**
2450 * @ngdoc method
2451 * @name AUTO.$injector#get
2452 * @methodOf AUTO.$injector
2453 *
2454 * @description
2455 * Return an instance of the service.
2456 *
2457 * @param {string} name The name of the instance to retrieve.
2458 * @return {*} The instance.
2459 */
2460
2461 /**
2462 * @ngdoc method
2463 * @name AUTO.$injector#invoke
2464 * @methodOf AUTO.$injector
2465 *
2466 * @description
2467 * Invoke the method and supply the method arguments from the `$injector`.
2468 *
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.
2474 */
2475
2476 /**
2477 * @ngdoc method
2478 * @name AUTO.$injector#instantiate
2479 * @methodOf AUTO.$injector
2480 * @description
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.
2483 *
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`.
2488 */
2489
2490 /**
2491 * @ngdoc method
2492 * @name AUTO.$injector#annotate
2493 * @methodOf AUTO.$injector
2494 *
2495 * @description
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.
2499 *
2500 * # Argument names
2501 *
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.
2504 * <pre>
2505 * // Given
2506 * function MyController($scope, $route) {
2507 * // ...
2508 * }
2509 *
2510 * // Then
2511 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
2512 * </pre>
2513 *
2514 * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
2515 * are supported.
2516 *
2517 * # The `$inject` property
2518 *
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.
2521 * <pre>
2522 * // Given
2523 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
2524 * // ...
2525 * }
2526 * // Define function dependencies
2527 * MyController.$inject = ['$scope', '$route'];
2528 *
2529 * // Then
2530 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
2531 * </pre>
2532 *
2533 * # The array notation
2534 *
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:
2538 *
2539 * <pre>
2540 * // We wish to write this (not minification / obfuscation safe)
2541 * injector.invoke(function($compile, $rootScope) {
2542 * // ...
2543 * });
2544 *
2545 * // We are forced to write break inlining
2546 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
2547 * // ...
2548 * };
2549 * tmpFn.$inject = ['$compile', '$rootScope'];
2550 * injector.invoke(tmpFn);
2551 *
2552 * // To better support inline function the inline annotation is supported
2553 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
2554 * // ...
2555 * }]);
2556 *
2557 * // Therefore
2558 * expect(injector.annotate(
2559 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
2560 * ).toEqual(['$compile', '$rootScope']);
2561 * </pre>
2562 *
2563 * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
2564 * above.
2565 *
2566 * @returns {Array.<string>} The names of the services which the function requires.
2567 */
2568
2569
2570
2571
2572 /**
2573 * @ngdoc object
2574 * @name AUTO.$provide
2575 *
2576 * @description
2577 *
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.
2580 *
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.
2583 *
2584 * <pre>
2585 * function GreetProvider() {
2586 * var salutation = 'Hello';
2587 *
2588 * this.salutation = function(text) {
2589 * salutation = text;
2590 * };
2591 *
2592 * this.$get = function() {
2593 * return function (name) {
2594 * return salutation + ' ' + name + '!';
2595 * };
2596 * };
2597 * }
2598 *
2599 * describe('Greeter', function(){
2600 *
2601 * beforeEach(module(function($provide) {
2602 * $provide.provider('greet', GreetProvider);
2603 * }));
2604 *
2605 * it('should greet', inject(function(greet) {
2606 * expect(greet('angular')).toEqual('Hello angular!');
2607 * }));
2608 *
2609 * it('should allow configuration of salutation', function() {
2610 * module(function(greetProvider) {
2611 * greetProvider.salutation('Ahoj');
2612 * });
2613 * inject(function(greet) {
2614 * expect(greet('angular')).toEqual('Ahoj angular!');
2615 * });
2616 * });
2617 * </pre>
2618 */
2619
2620 /**
2621 * @ngdoc method
2622 * @name AUTO.$provide#provider
2623 * @methodOf AUTO.$provide
2624 * @description
2625 *
2626 * Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
2627 *
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:
2630 *
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`.
2635 *
2636 * @returns {Object} registered provider instance
2637 */
2638
2639 /**
2640 * @ngdoc method
2641 * @name AUTO.$provide#factory
2642 * @methodOf AUTO.$provide
2643 * @description
2644 *
2645 * A short hand for configuring services if only `$get` method is required.
2646 *
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
2651 */
2652
2653
2654 /**
2655 * @ngdoc method
2656 * @name AUTO.$provide#service
2657 * @methodOf AUTO.$provide
2658 * @description
2659 *
2660 * A short hand for registering service of given class.
2661 *
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
2665 */
2666
2667
2668 /**
2669 * @ngdoc method
2670 * @name AUTO.$provide#value
2671 * @methodOf AUTO.$provide
2672 * @description
2673 *
2674 * A short hand for configuring services if the `$get` method is a constant.
2675 *
2676 * @param {string} name The name of the instance.
2677 * @param {*} value The value.
2678 * @returns {Object} registered provider instance
2679 */
2680
2681
2682 /**
2683 * @ngdoc method
2684 * @name AUTO.$provide#constant
2685 * @methodOf AUTO.$provide
2686 * @description
2687 *
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}.
2691 *
2692 * @param {string} name The name of the constant.
2693 * @param {*} value The constant value.
2694 * @returns {Object} registered instance
2695 */
2696
2697
2698 /**
2699 * @ngdoc method
2700 * @name AUTO.$provide#decorator
2701 * @methodOf AUTO.$provide
2702 * @description
2703 *
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.
2707 *
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:
2712 *
2713 * * `$delegate` - The original service instance, which can be monkey patched, configured,
2714 * decorated or delegated to.
2715 */
2716
2717
2718 function createInjector(modulesToLoad) {
2719 var INSTANTIATING = {},
2720 providerSuffix = 'Provider',
2721 path = [],
2722 loadedModules = new HashMap(),
2723 providerCache = {
2724 $provide: {
2725 provider: supportObject(provider),
2726 factory: supportObject(factory),
2727 service: supportObject(service),
2728 value: supportObject(value),
2729 constant: supportObject(constant),
2730 decorator: decorator
2731 }
2732 },
2733 providerInjector = createInternalInjector(providerCache, function() {
2734 throw Error("Unknown provider: " + path.join(' <- '));
2735 }),
2736 instanceCache = {},
2737 instanceInjector = (instanceCache.$injector =
2738 createInternalInjector(instanceCache, function(servicename) {
2739 var provider = providerInjector.get(servicename + providerSuffix);
2740 return instanceInjector.invoke(provider.$get, provider);
2741 }));
2742
2743
2744 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
2745
2746 return instanceInjector;
2747
2748 ////////////////////////////////////
2749 // $provider
2750 ////////////////////////////////////
2751
2752 function supportObject(delegate) {
2753 return function(key, value) {
2754 if (isObject(key)) {
2755 forEach(key, reverseParams(delegate));
2756 } else {
2757 return delegate(key, value);
2758 }
2759 }
2760 }
2761
2762 function provider(name, provider_) {
2763 if (isFunction(provider_) || isArray(provider_)) {
2764 provider_ = providerInjector.instantiate(provider_);
2765 }
2766 if (!provider_.$get) {
2767 throw Error('Provider ' + name + ' must define $get factory method.');
2768 }
2769 return providerCache[name + providerSuffix] = provider_;
2770 }
2771
2772 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
2773
2774 function service(name, constructor) {
2775 return factory(name, ['$injector', function($injector) {
2776 return $injector.instantiate(constructor);
2777 }]);
2778 }
2779
2780 function value(name, value) { return factory(name, valueFn(value)); }
2781
2782 function constant(name, value) {
2783 providerCache[name] = value;
2784 instanceCache[name] = value;
2785 }
2786
2787 function decorator(serviceName, decorFn) {
2788 var origProvider = providerInjector.get(serviceName + providerSuffix),
2789 orig$get = origProvider.$get;
2790
2791 origProvider.$get = function() {
2792 var origInstance = instanceInjector.invoke(orig$get, origProvider);
2793 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
2794 };
2795 }
2796
2797 ////////////////////////////////////
2798 // Module Loading
2799 ////////////////////////////////////
2800 function loadModules(modulesToLoad){
2801 var runBlocks = [];
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);
2808
2809 try {
2810 for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
2811 var invokeArgs = invokeQueue[i],
2812 provider = invokeArgs[0] == '$injector'
2813 ? providerInjector
2814 : providerInjector.get(invokeArgs[0]);
2815
2816 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
2817 }
2818 } catch (e) {
2819 if (e.message) e.message += ' from ' + module;
2820 throw e;
2821 }
2822 } else if (isFunction(module)) {
2823 try {
2824 runBlocks.push(providerInjector.invoke(module));
2825 } catch (e) {
2826 if (e.message) e.message += ' from ' + module;
2827 throw e;
2828 }
2829 } else if (isArray(module)) {
2830 try {
2831 runBlocks.push(providerInjector.invoke(module));
2832 } catch (e) {
2833 if (e.message) e.message += ' from ' + String(module[module.length - 1]);
2834 throw e;
2835 }
2836 } else {
2837 assertArgFn(module, 'module');
2838 }
2839 });
2840 return runBlocks;
2841 }
2842
2843 ////////////////////////////////////
2844 // internal Injector
2845 ////////////////////////////////////
2846
2847 function createInternalInjector(cache, factory) {
2848
2849 function getService(serviceName) {
2850 if (typeof serviceName !== 'string') {
2851 throw Error('Service name expected');
2852 }
2853 if (cache.hasOwnProperty(serviceName)) {
2854 if (cache[serviceName] === INSTANTIATING) {
2855 throw Error('Circular dependency: ' + path.join(' <- '));
2856 }
2857 return cache[serviceName];
2858 } else {
2859 try {
2860 path.unshift(serviceName);
2861 cache[serviceName] = INSTANTIATING;
2862 return cache[serviceName] = factory(serviceName);
2863 } finally {
2864 path.shift();
2865 }
2866 }
2867 }
2868
2869 function invoke(fn, self, locals){
2870 var args = [],
2871 $inject = annotate(fn),
2872 length, i,
2873 key;
2874
2875 for(i = 0, length = $inject.length; i < length; i++) {
2876 key = $inject[i];
2877 args.push(
2878 locals && locals.hasOwnProperty(key)
2879 ? locals[key]
2880 : getService(key)
2881 );
2882 }
2883 if (!fn.$inject) {
2884 // this means that we must be an array.
2885 fn = fn[length];
2886 }
2887
2888
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);
2903 }
2904 }
2905
2906 function instantiate(Type, locals) {
2907 var Constructor = function() {},
2908 instance, returnedValue;
2909
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);
2915
2916 return isObject(returnedValue) ? returnedValue : instance;
2917 }
2918
2919 return {
2920 invoke: invoke,
2921 instantiate: instantiate,
2922 get: getService,
2923 annotate: annotate
2924 };
2925 }
2926 }
2927
2928 /**
2929 * @ngdoc function
2930 * @name ng.$anchorScroll
2931 * @requires $window
2932 * @requires $location
2933 * @requires $rootScope
2934 *
2935 * @description
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}.
2939 *
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()`.
2942 */
2943 function $AnchorScrollProvider() {
2944
2945 var autoScrollingEnabled = true;
2946
2947 this.disableAutoScrolling = function() {
2948 autoScrollingEnabled = false;
2949 };
2950
2951 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
2952 var document = $window.document;
2953
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) {
2959 var result = null;
2960 forEach(list, function(element) {
2961 if (!result && lowercase(element.nodeName) === 'a') result = element;
2962 });
2963 return result;
2964 }
2965
2966 function scroll() {
2967 var hash = $location.hash(), elm;
2968
2969 // empty hash, scroll to the top of the page
2970 if (!hash) $window.scrollTo(0, 0);
2971
2972 // element with given id
2973 else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
2974
2975 // first anchor with given name :-D
2976 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
2977
2978 // no element and hash == 'top', scroll to the top of the page
2979 else if (hash === 'top') $window.scrollTo(0, 0);
2980 }
2981
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);
2988 });
2989 }
2990
2991 return scroll;
2992 }];
2993 }
2994
2995 /**
2996 * ! This is a private undocumented service !
2997 *
2998 * @name ng.$browser
2999 * @requires $log
3000 * @description
3001 * This object has two goals:
3002 *
3003 * - hide all the global state in the browser caused by the window object
3004 * - abstract away all the browser specific features and inconsistencies
3005 *
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.
3009 */
3010 /**
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
3016 */
3017 function Browser(window, document, $log, $sniffer) {
3018 var self = this,
3019 rawDocument = document[0],
3020 location = window.location,
3021 history = window.history,
3022 setTimeout = window.setTimeout,
3023 clearTimeout = window.clearTimeout,
3024 pendingDeferIds = {};
3025
3026 self.isMock = false;
3027
3028 var outstandingRequestCount = 0;
3029 var outstandingRequestCallbacks = [];
3030
3031 // TODO(vojta): remove this temporary api
3032 self.$$completeOutstandingRequest = completeOutstandingRequest;
3033 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
3034
3035 /**
3036 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
3037 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
3038 */
3039 function completeOutstandingRequest(fn) {
3040 try {
3041 fn.apply(null, sliceArgs(arguments, 1));
3042 } finally {
3043 outstandingRequestCount--;
3044 if (outstandingRequestCount === 0) {
3045 while(outstandingRequestCallbacks.length) {
3046 try {
3047 outstandingRequestCallbacks.pop()();
3048 } catch (e) {
3049 $log.error(e);
3050 }
3051 }
3052 }
3053 }
3054 }
3055
3056 /**
3057 * @private
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
3061 */
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(); });
3067
3068 if (outstandingRequestCount === 0) {
3069 callback();
3070 } else {
3071 outstandingRequestCallbacks.push(callback);
3072 }
3073 };
3074
3075 //////////////////////////////////////////////////////////////
3076 // Poll Watcher API
3077 //////////////////////////////////////////////////////////////
3078 var pollFns = [],
3079 pollTimeout;
3080
3081 /**
3082 * @name ng.$browser#addPollFn
3083 * @methodOf ng.$browser
3084 *
3085 * @param {function()} fn Poll function to add
3086 *
3087 * @description
3088 * Adds a function to the list of functions that poller periodically executes,
3089 * and starts polling if not started yet.
3090 *
3091 * @returns {function()} the added function
3092 */
3093 self.addPollFn = function(fn) {
3094 if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
3095 pollFns.push(fn);
3096 return fn;
3097 };
3098
3099 /**
3100 * @param {number} interval How often should browser call poll functions (ms)
3101 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
3102 *
3103 * @description
3104 * Configures the poller to run in the specified intervals, using the specified
3105 * setTimeout fn and kicks it off.
3106 */
3107 function startPoller(interval, setTimeout) {
3108 (function check() {
3109 forEach(pollFns, function(pollFn){ pollFn(); });
3110 pollTimeout = setTimeout(check, interval);
3111 })();
3112 }
3113
3114 //////////////////////////////////////////////////////////////
3115 // URL API
3116 //////////////////////////////////////////////////////////////
3117
3118 var lastBrowserUrl = location.href,
3119 baseElement = document.find('base');
3120
3121 /**
3122 * @name ng.$browser#url
3123 * @methodOf ng.$browser
3124 *
3125 * @description
3126 * GETTER:
3127 * Without any argument, this method just returns current value of location.href.
3128 *
3129 * SETTER:
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
3134 *
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.
3137 *
3138 * @param {string} url New url (when used as setter)
3139 * @param {boolean=} replace Should new url replace current history record ?
3140 */
3141 self.url = function(url, replace) {
3142 // setter
3143 if (url) {
3144 if (lastBrowserUrl == url) return;
3145 lastBrowserUrl = url;
3146 if ($sniffer.history) {
3147 if (replace) history.replaceState(null, '', url);
3148 else {
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'));
3152 }
3153 } else {
3154 if (replace) location.replace(url);
3155 else location.href = url;
3156 }
3157 return self;
3158 // getter
3159 } else {
3160 // the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
3161 return location.href.replace(/%27/g,"'");
3162 }
3163 };
3164
3165 var urlChangeListeners = [],
3166 urlChangeInit = false;
3167
3168 function fireUrlChange() {
3169 if (lastBrowserUrl == self.url()) return;
3170
3171 lastBrowserUrl = self.url();
3172 forEach(urlChangeListeners, function(listener) {
3173 listener(self.url());
3174 });
3175 }
3176
3177 /**
3178 * @name ng.$browser#onUrlChange
3179 * @methodOf ng.$browser
3180 * @TODO(vojta): refactor to use node's syntax for events
3181 *
3182 * @description
3183 * Register callback function that will be called, when url changes.
3184 *
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
3189 *
3190 * It's not called when url is changed by $browser.url() method
3191 *
3192 * The listener gets called with new url as parameter.
3193 *
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.
3196 *
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.
3199 */
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
3205
3206 // html5 history api - popstate event
3207 if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
3208 // hashchange event
3209 if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
3210 // polling
3211 else self.addPollFn(fireUrlChange);
3212
3213 urlChangeInit = true;
3214 }
3215
3216 urlChangeListeners.push(callback);
3217 return callback;
3218 };
3219
3220 //////////////////////////////////////////////////////////////
3221 // Misc API
3222 //////////////////////////////////////////////////////////////
3223
3224 /**
3225 * Returns current <base href>
3226 * (always relative - without domain)
3227 *
3228 * @returns {string=}
3229 */
3230 self.baseHref = function() {
3231 var href = baseElement.attr('href');
3232 return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
3233 };
3234
3235 //////////////////////////////////////////////////////////////
3236 // Cookies API
3237 //////////////////////////////////////////////////////////////
3238 var lastCookies = {};
3239 var lastCookieString = '';
3240 var cookiePath = self.baseHref();
3241
3242 /**
3243 * @name ng.$browser#cookies
3244 * @methodOf ng.$browser
3245 *
3246 * @param {string=} name Cookie name
3247 * @param {string=} value Cokkie value
3248 *
3249 * @description
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.
3252 *
3253 * The return values vary depending on the arguments that the method was called with as follows:
3254 * <ul>
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>
3258 * </ul>
3259 *
3260 * @returns {Object} Hash of all cookies (if called without any parameter)
3261 */
3262 self.cookies = function(name, value) {
3263 var cookieLength, cookieArray, cookie, i, index;
3264
3265 if (name) {
3266 if (value === undefined) {
3267 rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
3268 } else {
3269 if (isString(value)) {
3270 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
3271
3272 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
3273 // - 300 cookies
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)!");
3279 }
3280 }
3281 }
3282 } else {
3283 if (rawDocument.cookie !== lastCookieString) {
3284 lastCookieString = rawDocument.cookie;
3285 cookieArray = lastCookieString.split("; ");
3286 lastCookies = {};
3287
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));
3298 }
3299 }
3300 }
3301 }
3302 return lastCookies;
3303 }
3304 };
3305
3306
3307 /**
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()`.
3313 *
3314 * @description
3315 * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
3316 *
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()`.
3320 *
3321 */
3322 self.defer = function(fn, delay) {
3323 var timeoutId;
3324 outstandingRequestCount++;
3325 timeoutId = setTimeout(function() {
3326 delete pendingDeferIds[timeoutId];
3327 completeOutstandingRequest(fn);
3328 }, delay || 0);
3329 pendingDeferIds[timeoutId] = true;
3330 return timeoutId;
3331 };
3332
3333
3334 /**
3335 * @name ng.$browser#defer.cancel
3336 * @methodOf ng.$browser.defer
3337 *
3338 * @description
3339 * Cancels a defered task identified with `deferId`.
3340 *
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.
3343 */
3344 self.defer.cancel = function(deferId) {
3345 if (pendingDeferIds[deferId]) {
3346 delete pendingDeferIds[deferId];
3347 clearTimeout(deferId);
3348 completeOutstandingRequest(noop);
3349 return true;
3350 }
3351 return false;
3352 };
3353
3354 }
3355
3356 function $BrowserProvider(){
3357 this.$get = ['$window', '$log', '$sniffer', '$document',
3358 function( $window, $log, $sniffer, $document){
3359 return new Browser($window, $document, $log, $sniffer);
3360 }];
3361 }
3362
3363 /**
3364 * @ngdoc object
3365 * @name ng.$cacheFactory
3366 *
3367 * @description
3368 * Factory that constructs cache objects.
3369 *
3370 *
3371 * @param {string} cacheId Name or id of the newly created cache.
3372 * @param {object=} options Options object that specifies the cache behavior. Properties:
3373 *
3374 * - `{number=}` `capacity` — turns the cache into LRU cache.
3375 *
3376 * @returns {object} Newly created cache object with the following set of methods:
3377 *
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.
3384 *
3385 */
3386 function $CacheFactoryProvider() {
3387
3388 this.$get = function() {
3389 var caches = {};
3390
3391 function cacheFactory(cacheId, options) {
3392 if (cacheId in caches) {
3393 throw Error('cacheId ' + cacheId + ' taken');
3394 }
3395
3396 var size = 0,
3397 stats = extend({}, options, {id: cacheId}),
3398 data = {},
3399 capacity = (options && options.capacity) || Number.MAX_VALUE,
3400 lruHash = {},
3401 freshEnd = null,
3402 staleEnd = null;
3403
3404 return caches[cacheId] = {
3405
3406 put: function(key, value) {
3407 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
3408
3409 refresh(lruEntry);
3410
3411 if (isUndefined(value)) return;
3412 if (!(key in data)) size++;
3413 data[key] = value;
3414
3415 if (size > capacity) {
3416 this.remove(staleEnd.key);
3417 }
3418 },
3419
3420
3421 get: function(key) {
3422 var lruEntry = lruHash[key];
3423
3424 if (!lruEntry) return;
3425
3426 refresh(lruEntry);
3427
3428 return data[key];
3429 },
3430
3431
3432 remove: function(key) {
3433 var lruEntry = lruHash[key];
3434
3435 if (!lruEntry) return;
3436
3437 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
3438 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
3439 link(lruEntry.n,lruEntry.p);
3440
3441 delete lruHash[key];
3442 delete data[key];
3443 size--;
3444 },
3445
3446
3447 removeAll: function() {
3448 data = {};
3449 size = 0;
3450 lruHash = {};
3451 freshEnd = staleEnd = null;
3452 },
3453
3454
3455 destroy: function() {
3456 data = null;
3457 stats = null;
3458 lruHash = null;
3459 delete caches[cacheId];
3460 },
3461
3462
3463 info: function() {
3464 return extend({}, stats, {size: size});
3465 }
3466 };
3467
3468
3469 /**
3470 * makes the `entry` the freshEnd of the LRU linked list
3471 */
3472 function refresh(entry) {
3473 if (entry != freshEnd) {
3474 if (!staleEnd) {
3475 staleEnd = entry;
3476 } else if (staleEnd == entry) {
3477 staleEnd = entry.n;
3478 }
3479
3480 link(entry.n, entry.p);
3481 link(entry, freshEnd);
3482 freshEnd = entry;
3483 freshEnd.n = null;
3484 }
3485 }
3486
3487
3488 /**
3489 * bidirectionally links two entries of the LRU linked list
3490 */
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
3495 }
3496 }
3497 }
3498
3499
3500 cacheFactory.info = function() {
3501 var info = {};
3502 forEach(caches, function(cache, cacheId) {
3503 info[cacheId] = cache.info();
3504 });
3505 return info;
3506 };
3507
3508
3509 cacheFactory.get = function(cacheId) {
3510 return caches[cacheId];
3511 };
3512
3513
3514 return cacheFactory;
3515 };
3516 }
3517
3518 /**
3519 * @ngdoc object
3520 * @name ng.$templateCache
3521 *
3522 * @description
3523 * Cache used for storing html templates.
3524 *
3525 * See {@link ng.$cacheFactory $cacheFactory}.
3526 *
3527 */
3528 function $TemplateCacheProvider() {
3529 this.$get = ['$cacheFactory', function($cacheFactory) {
3530 return $cacheFactory('templates');
3531 }];
3532 }
3533
3534 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
3535 *
3536 * DOM-related variables:
3537 *
3538 * - "node" - DOM Node
3539 * - "element" - DOM Element or Node
3540 * - "$node" or "$element" - jqLite-wrapped node or element
3541 *
3542 *
3543 * Compiler related stuff:
3544 *
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)
3549 */
3550
3551
3552 var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
3553
3554
3555 /**
3556 * @ngdoc function
3557 * @name ng.$compile
3558 * @function
3559 *
3560 * @description
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.
3563 *
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.
3568 *
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.
3572 *
3573 <doc:example module="compile">
3574 <doc:source>
3575 <script>
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) {
3583 scope.$watch(
3584 function(scope) {
3585 // watch the 'compile' expression for changes
3586 return scope.$eval(attrs.compile);
3587 },
3588 function(value) {
3589 // when the 'compile' expression changes
3590 // assign it into the current DOM
3591 element.html(value);
3592
3593 // compile the new DOM and link it to the current
3594 // scope.
3595 // NOTE: we only compile .childNodes so that
3596 // we don't get into infinite loop compiling ourselves
3597 $compile(element.contents())(scope);
3598 }
3599 );
3600 };
3601 })
3602 });
3603
3604 function Ctrl($scope) {
3605 $scope.name = 'Angular';
3606 $scope.html = 'Hello {{name}}';
3607 }
3608 </script>
3609 <div ng-controller="Ctrl">
3610 <input ng-model="name"> <br>
3611 <textarea ng-model="html"></textarea> <br>
3612 <div compile="html"></div>
3613 </div>
3614 </doc:source>
3615 <doc:scenario>
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!');
3620 });
3621 </doc:scenario>
3622 </doc:example>
3623
3624 *
3625 *
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:
3632 *
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:
3638 *
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.
3641 *
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.
3644 *
3645 * After linking the view is not updated until after a call to $digest which typically is done by
3646 * Angular automatically.
3647 *
3648 * If you need access to the bound view, there are two ways to do it:
3649 *
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.
3652 * <pre>
3653 * var element = $compile('<p>{{total}}</p>')(scope);
3654 * </pre>
3655 *
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:
3659 * <pre>
3660 * var templateHTML = angular.element('<p>{{total}}</p>'),
3661 * scope = ....;
3662 *
3663 * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
3664 * //attach the clone to DOM document at the right place
3665 * });
3666 *
3667 * //now we have reference to the cloned DOM via `clone`
3668 * </pre>
3669 *
3670 *
3671 * For information on how the compiler works, see the
3672 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
3673 */
3674
3675
3676 /**
3677 * @ngdoc service
3678 * @name ng.$compileProvider
3679 * @function
3680 *
3681 * @description
3682 */
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):/;
3691
3692
3693 /**
3694 * @ngdoc function
3695 * @name ng.$compileProvider#directive
3696 * @methodOf ng.$compileProvider
3697 * @function
3698 *
3699 * @description
3700 * Register a new directives with the compiler.
3701 *
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
3705 * info.
3706 * @returns {ng.$compileProvider} Self for chaining.
3707 */
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) {
3717 try {
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);
3723 }
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);
3729 } catch (e) {
3730 $exceptionHandler(e);
3731 }
3732 });
3733 return directives;
3734 }]);
3735 }
3736 hasDirectives[name].push(directiveFactory);
3737 } else {
3738 forEach(name, reverseParams(registerDirective));
3739 }
3740 return this;
3741 };
3742
3743
3744 /**
3745 * @ngdoc function
3746 * @name ng.$compileProvider#urlSanitizationWhitelist
3747 * @methodOf ng.$compileProvider
3748 * @function
3749 *
3750 * @description
3751 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
3752 * urls during a[href] sanitization.
3753 *
3754 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
3755 *
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.
3760 *
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.
3764 */
3765 this.urlSanitizationWhitelist = function(regexp) {
3766 if (isDefined(regexp)) {
3767 urlSanitizationWhitelist = regexp;
3768 return this;
3769 }
3770 return urlSanitizationWhitelist;
3771 };
3772
3773
3774 this.$get = [
3775 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
3776 '$controller', '$rootScope', '$document',
3777 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
3778 $controller, $rootScope, $document) {
3779
3780 var Attributes = function(element, attr) {
3781 this.$$element = element;
3782 this.$attr = attr || {};
3783 };
3784
3785 Attributes.prototype = {
3786 $normalize: directiveNormalize,
3787
3788
3789 /**
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.
3795 * Defaults to true.
3796 * @param {string=} attrName Optional none normalized name. Defaults to key.
3797 */
3798 $set: function(key, value, writeAttr, attrName) {
3799 var booleanKey = getBooleanAttrName(this.$$element[0], key),
3800 $$observers = this.$$observers,
3801 normalizedVal;
3802
3803 if (booleanKey) {
3804 this.$$element.prop(key, value);
3805 attrName = booleanKey;
3806 }
3807
3808 this[key] = value;
3809
3810 // translate normalized key to actual key
3811 if (attrName) {
3812 this.$attr[key] = attrName;
3813 } else {
3814 attrName = this.$attr[key];
3815 if (!attrName) {
3816 this.$attr[key] = attrName = snake_case(key, '-');
3817 }
3818 }
3819
3820
3821 // sanitize a[href] values
3822 if (nodeName_(this.$$element[0]) === 'A' && key === 'href') {
3823 urlSanitizationNode.setAttribute('href', value);
3824
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;
3829 }
3830 }
3831
3832
3833 if (writeAttr !== false) {
3834 if (value === null || value === undefined) {
3835 this.$$element.removeAttr(attrName);
3836 } else {
3837 this.$$element.attr(attrName, value);
3838 }
3839 }
3840
3841 // fire observers
3842 $$observers && forEach($$observers[key], function(fn) {
3843 try {
3844 fn(value);
3845 } catch (e) {
3846 $exceptionHandler(e);
3847 }
3848 });
3849 },
3850
3851
3852 /**
3853 * Observe an interpolated attribute.
3854 * The observer will never be called, if given attribute is not interpolated.
3855 *
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.
3859 */
3860 $observe: function(key, fn) {
3861 var attrs = this,
3862 $$observers = (attrs.$$observers || (attrs.$$observers = {})),
3863 listeners = ($$observers[key] || ($$observers[key] = []));
3864
3865 listeners.push(fn);
3866 $rootScope.$evalAsync(function() {
3867 if (!listeners.$$inter) {
3868 // no one registered attribute interpolation function, so lets call it manually
3869 fn(attrs[key]);
3870 }
3871 });
3872 return fn;
3873 }
3874 };
3875
3876 var urlSanitizationNode = $document[0].createElement('a'),
3877 startSymbol = $interpolate.startSymbol(),
3878 endSymbol = $interpolate.endSymbol(),
3879 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
3880 ? identity
3881 : function denormalizeTemplate(template) {
3882 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
3883 };
3884
3885
3886 return compile;
3887
3888 //================================
3889
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);
3894 }
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];
3900 }
3901 });
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!!!
3909 : $compileNodes;
3910
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);
3916 }
3917 }
3918 safeAddClass($linkNode, 'ng-scope');
3919 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
3920 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
3921 return $linkNode;
3922 };
3923 }
3924
3925 function wrongMode(localName, mode) {
3926 throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
3927 }
3928
3929 function safeAddClass($element, className) {
3930 try {
3931 $element.addClass(className);
3932 } catch(e) {
3933 // ignore, since it means that we are trying to set class on
3934 // SVG element, where class name is read-only.
3935 }
3936 }
3937
3938 /**
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.
3943 *
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.
3952 */
3953 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
3954 var linkFns = [],
3955 nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
3956
3957 for(var i = 0; i < nodeList.length; i++) {
3958 attrs = new Attributes();
3959
3960 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
3961 directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
3962
3963 nodeLinkFn = (directives.length)
3964 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
3965 : null;
3966
3967 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
3968 ? null
3969 : compileNodes(nodeList[i].childNodes,
3970 nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
3971
3972 linkFns.push(nodeLinkFn);
3973 linkFns.push(childLinkFn);
3974 linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
3975 }
3976
3977 // return a linking function if we have found anything, null otherwise
3978 return linkFnFound ? compositeLinkFn : null;
3979
3980 function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
3981 var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
3982
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]);
3987 }
3988
3989 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
3990 node = stableNodeList[n];
3991 nodeLinkFn = linkFns[i++];
3992 childLinkFn = linkFns[i++];
3993
3994 if (nodeLinkFn) {
3995 if (nodeLinkFn.scope) {
3996 childScope = scope.$new(isObject(nodeLinkFn.scope));
3997 jqLite(node).data('$scope', childScope);
3998 } else {
3999 childScope = scope;
4000 }
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;
4008
4009 return transcludeFn(transcludeScope, cloneFn).
4010 bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
4011 };
4012 })(childTranscludeFn || transcludeFn)
4013 );
4014 } else {
4015 nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
4016 }
4017 } else if (childLinkFn) {
4018 childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
4019 }
4020 }
4021 }
4022 }
4023
4024
4025 /**
4026 * Looks for directives on the given node and adds them to the directive collection which is
4027 * sorted.
4028 *
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.
4034 */
4035 function collectDirectives(node, directives, attrs, maxPriority) {
4036 var nodeType = node.nodeType,
4037 attrsMap = attrs.$attr,
4038 match,
4039 className;
4040
4041 switch(nodeType) {
4042 case 1: /* Element */
4043 // use the node name: <directive>
4044 addDirective(directives,
4045 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
4046
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++) {
4050 attr = nAttrs[j];
4051 if (attr.specified) {
4052 name = attr.name;
4053 nName = directiveNormalize(name.toLowerCase());
4054 attrsMap[nName] = name;
4055 attrs[nName] = value = trim((msie && name == 'href')
4056 ? decodeURIComponent(node.getAttribute(name, 2))
4057 : attr.value);
4058 if (getBooleanAttrName(node, nName)) {
4059 attrs[nName] = true; // presence means true
4060 }
4061 addAttrInterpolateDirective(node, directives, value, nName);
4062 addDirective(directives, nName, 'A', maxPriority);
4063 }
4064 }
4065
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]);
4073 }
4074 className = className.substr(match.index + match[0].length);
4075 }
4076 }
4077 break;
4078 case 3: /* Text Node */
4079 addTextInterpolateDirective(directives, node.nodeValue);
4080 break;
4081 case 8: /* Comment */
4082 try {
4083 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
4084 if (match) {
4085 nName = directiveNormalize(match[1]);
4086 if (addDirective(directives, nName, 'M', maxPriority)) {
4087 attrs[nName] = trim(match[2]);
4088 }
4089 }
4090 } catch (e) {
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.)
4093 }
4094 break;
4095 }
4096
4097 directives.sort(byPriority);
4098 return directives;
4099 }
4100
4101
4102 /**
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.
4106 *
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.
4115 * @returns linkFn
4116 */
4117 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection) {
4118 var terminalPriority = -Number.MAX_VALUE,
4119 preLinkFns = [],
4120 postLinkFns = [],
4121 newScopeDirective = null,
4122 newIsolateScopeDirective = null,
4123 templateDirective = null,
4124 $compileNode = templateAttrs.$$element = jqLite(compileNode),
4125 directive,
4126 directiveName,
4127 $template,
4128 transcludeDirective,
4129 childTranscludeFn = transcludeFn,
4130 controllerDirectives,
4131 linkFn,
4132 directiveValue;
4133
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;
4138
4139 if (terminalPriority > directive.priority) {
4140 break; // prevent further processing of directives
4141 }
4142
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;
4148 }
4149 safeAddClass($compileNode, 'ng-scope');
4150 newScopeDirective = newScopeDirective || directive;
4151 }
4152
4153 directiveName = directive.name;
4154
4155 if (directiveValue = directive.controller) {
4156 controllerDirectives = controllerDirectives || {};
4157 assertNoDuplicate("'" + directiveName + "' controller",
4158 controllerDirectives[directiveName], directive, $compileNode);
4159 controllerDirectives[directiveName] = directive;
4160 }
4161
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);
4173 } else {
4174 $template = jqLite(JQLiteClone(compileNode)).contents();
4175 $compileNode.html(''); // clear contents
4176 childTranscludeFn = compile($template, transcludeFn);
4177 }
4178 }
4179
4180 if ((directiveValue = directive.template)) {
4181 assertNoDuplicate('template', templateDirective, directive, $compileNode);
4182 templateDirective = directive;
4183 directiveValue = denormalizeTemplate(directiveValue);
4184
4185 if (directive.replace) {
4186 $template = jqLite('<div>' +
4187 trim(directiveValue) +
4188 '</div>').contents();
4189 compileNode = $template[0];
4190
4191 if ($template.length != 1 || compileNode.nodeType !== 1) {
4192 throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
4193 }
4194
4195 replaceWith(jqCollection, $compileNode, compileNode);
4196
4197 var newTemplateAttrs = {$attr: {}};
4198
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(
4205 collectDirectives(
4206 compileNode,
4207 directives.splice(i + 1, directives.length - (i + 1)),
4208 newTemplateAttrs
4209 )
4210 );
4211 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
4212
4213 ii = directives.length;
4214 } else {
4215 $compileNode.html(directiveValue);
4216 }
4217 }
4218
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,
4224 childTranscludeFn);
4225 ii = directives.length;
4226 } else if (directive.compile) {
4227 try {
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);
4233 }
4234 } catch (e) {
4235 $exceptionHandler(e, startingTag($compileNode));
4236 }
4237 }
4238
4239 if (directive.terminal) {
4240 nodeLinkFn.terminal = true;
4241 terminalPriority = Math.max(terminalPriority, directive.priority);
4242 }
4243
4244 }
4245
4246 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
4247 nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
4248
4249 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
4250 return nodeLinkFn;
4251
4252 ////////////////////
4253
4254 function addLinkFns(pre, post) {
4255 if (pre) {
4256 pre.require = directive.require;
4257 preLinkFns.push(pre);
4258 }
4259 if (post) {
4260 post.require = directive.require;
4261 postLinkFns.push(post);
4262 }
4263 }
4264
4265
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);
4271 if (value == '^') {
4272 retrievalMethod = 'inheritedData';
4273 }
4274 optional = optional || value == '?';
4275 }
4276 value = $element[retrievalMethod]('$' + require + 'Controller');
4277 if (!value && !optional) {
4278 throw Error("No controller: " + require);
4279 }
4280 return value;
4281 } else if (isArray(require)) {
4282 value = [];
4283 forEach(require, function(require) {
4284 value.push(getControllers(require, $element));
4285 });
4286 }
4287 return value;
4288 }
4289
4290
4291 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
4292 var attrs, $element, i, ii, linkFn, controller;
4293
4294 if (compileNode === linkNode) {
4295 attrs = templateAttrs;
4296 } else {
4297 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
4298 }
4299 $element = attrs.$$element;
4300
4301 if (newIsolateScopeDirective) {
4302 var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
4303
4304 var parentScope = scope.$parent || scope;
4305
4306 forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
4307 var match = definiton.match(LOCAL_REGEXP) || [],
4308 attrName = match[2]|| scopeName,
4309 mode = match[1], // @, =, or &
4310 lastValue,
4311 parentGet, parentSet;
4312
4313 scope.$$isolateBindings[scopeName] = mode + attrName;
4314
4315 switch (mode) {
4316
4317 case '@': {
4318 attrs.$observe(attrName, function(value) {
4319 scope[scopeName] = value;
4320 });
4321 attrs.$$observers[attrName].$$scope = parentScope;
4322 break;
4323 }
4324
4325 case '=': {
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 + ')');
4332 };
4333 lastValue = scope[scopeName] = parentGet(parentScope);
4334 scope.$watch(function parentValueWatch() {
4335 var parentValue = parentGet(parentScope);
4336
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;
4342 } else {
4343 // if the parent can be assigned then do so
4344 parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
4345 }
4346 }
4347 return parentValue;
4348 });
4349 break;
4350 }
4351
4352 case '&': {
4353 parentGet = $parse(attrs[attrName]);
4354 scope[scopeName] = function(locals) {
4355 return parentGet(parentScope, locals);
4356 };
4357 break;
4358 }
4359
4360 default: {
4361 throw Error('Invalid isolate scope definition for directive ' +
4362 newIsolateScopeDirective.name + ': ' + definiton);
4363 }
4364 }
4365 });
4366 }
4367
4368 if (controllerDirectives) {
4369 forEach(controllerDirectives, function(directive) {
4370 var locals = {
4371 $scope: scope,
4372 $element: $element,
4373 $attrs: attrs,
4374 $transclude: boundTranscludeFn
4375 };
4376
4377 controller = directive.controller;
4378 if (controller == '@') {
4379 controller = attrs[directive.name];
4380 }
4381
4382 $element.data(
4383 '$' + directive.name + 'Controller',
4384 $controller(controller, locals));
4385 });
4386 }
4387
4388 // PRELINKING
4389 for(i = 0, ii = preLinkFns.length; i < ii; i++) {
4390 try {
4391 linkFn = preLinkFns[i];
4392 linkFn(scope, $element, attrs,
4393 linkFn.require && getControllers(linkFn.require, $element));
4394 } catch (e) {
4395 $exceptionHandler(e, startingTag($element));
4396 }
4397 }
4398
4399 // RECURSION
4400 childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
4401
4402 // POSTLINKING
4403 for(i = 0, ii = postLinkFns.length; i < ii; i++) {
4404 try {
4405 linkFn = postLinkFns[i];
4406 linkFn(scope, $element, attrs,
4407 linkFn.require && getControllers(linkFn.require, $element));
4408 } catch (e) {
4409 $exceptionHandler(e, startingTag($element));
4410 }
4411 }
4412 }
4413 }
4414
4415
4416 /**
4417 * looks up the directive and decorates it with exception handling and proper parameters. We
4418 * call this the boundDirective.
4419 *
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:
4423 *
4424 * * `E`: element name
4425 * * `A': attribute
4426 * * `C`: class
4427 * * `M`: comment
4428 * @returns true if directive was added.
4429 */
4430 function addDirective(tDirectives, name, location, maxPriority) {
4431 var match = false;
4432 if (hasDirectives.hasOwnProperty(name)) {
4433 for(var directive, directives = $injector.get(name + Suffix),
4434 i = 0, ii = directives.length; i<ii; i++) {
4435 try {
4436 directive = directives[i];
4437 if ( (maxPriority === undefined || maxPriority > directive.priority) &&
4438 directive.restrict.indexOf(location) != -1) {
4439 tDirectives.push(directive);
4440 match = true;
4441 }
4442 } catch(e) { $exceptionHandler(e); }
4443 }
4444 }
4445 return match;
4446 }
4447
4448
4449 /**
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.
4453 *
4454 * @param {object} dst destination attributes (original DOM)
4455 * @param {object} src source attributes (from the directive template)
4456 */
4457 function mergeTemplateAttributes(dst, src) {
4458 var srcAttr = src.$attr,
4459 dstAttr = dst.$attr,
4460 $element = dst.$$element;
4461
4462 // reapply the old attributes to the new element
4463 forEach(dst, function(value, key) {
4464 if (key.charAt(0) != '$') {
4465 if (src[key]) {
4466 value += (key === 'style' ? ';' : ' ') + src[key];
4467 }
4468 dst.$set(key, value, true, srcAttr[key]);
4469 }
4470 });
4471
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)) {
4480 dst[key] = value;
4481 dstAttr[key] = srcAttr[key];
4482 }
4483 });
4484 }
4485
4486
4487 function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
4488 $rootElement, replace, childTranscludeFn) {
4489 var linkQueue = [],
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
4497 });
4498
4499 $compileNode.html('');
4500
4501 $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
4502 success(function(content) {
4503 var compileNode, tempTemplateAttrs, $template;
4504
4505 content = denormalizeTemplate(content);
4506
4507 if (replace) {
4508 $template = jqLite('<div>' + trim(content) + '</div>').contents();
4509 compileNode = $template[0];
4510
4511 if ($template.length != 1 || compileNode.nodeType !== 1) {
4512 throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
4513 }
4514
4515 tempTemplateAttrs = {$attr: {}};
4516 replaceWith($rootElement, $compileNode, compileNode);
4517 collectDirectives(compileNode, directives, tempTemplateAttrs);
4518 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
4519 } else {
4520 compileNode = beforeTemplateCompileNode;
4521 $compileNode.html(content);
4522 }
4523
4524 directives.unshift(derivedSyncDirective);
4525 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
4526 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
4527
4528
4529 while(linkQueue.length) {
4530 var controller = linkQueue.pop(),
4531 linkRootElement = linkQueue.pop(),
4532 beforeTemplateLinkNode = linkQueue.pop(),
4533 scope = linkQueue.pop(),
4534 linkNode = compileNode;
4535
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);
4540 }
4541
4542 afterTemplateNodeLinkFn(function() {
4543 beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
4544 }, scope, linkNode, $rootElement, controller);
4545 }
4546 linkQueue = null;
4547 }).
4548 error(function(response, code, headers, config) {
4549 throw Error('Failed to load template: ' + config.url);
4550 });
4551
4552 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
4553 if (linkQueue) {
4554 linkQueue.push(scope);
4555 linkQueue.push(node);
4556 linkQueue.push(rootElement);
4557 linkQueue.push(controller);
4558 } else {
4559 afterTemplateNodeLinkFn(function() {
4560 beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
4561 }, scope, node, rootElement, controller);
4562 }
4563 };
4564 }
4565
4566
4567 /**
4568 * Sorting function for bound directives.
4569 */
4570 function byPriority(a, b) {
4571 return b.priority - a.priority;
4572 }
4573
4574
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));
4579 }
4580 }
4581
4582
4583 function addTextInterpolateDirective(directives, text) {
4584 var interpolateFn = $interpolate(text, true);
4585 if (interpolateFn) {
4586 directives.push({
4587 priority: 0,
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;
4595 });
4596 })
4597 });
4598 }
4599 }
4600
4601
4602 function addAttrInterpolateDirective(node, directives, value, name) {
4603 var interpolateFn = $interpolate(value, true);
4604
4605 // no interpolation found -> ignore
4606 if (!interpolateFn) return;
4607
4608
4609 directives.push({
4610 priority: 100,
4611 compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
4612 var $$observers = (attr.$$observers || (attr.$$observers = {}));
4613
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);
4618 }
4619
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);
4625 });
4626 })
4627 });
4628 }
4629
4630
4631 /**
4632 * This is a special jqLite.replaceWith, which can replace items which
4633 * have no parents, provided that the containing jqLite collection is provided.
4634 *
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.
4640 */
4641 function replaceWith($rootElement, $element, newNode) {
4642 var oldNode = $element[0],
4643 parent = oldNode.parentNode,
4644 i, ii;
4645
4646 if ($rootElement) {
4647 for(i = 0, ii = $rootElement.length; i < ii; i++) {
4648 if ($rootElement[i] == oldNode) {
4649 $rootElement[i] = newNode;
4650 break;
4651 }
4652 }
4653 }
4654
4655 if (parent) {
4656 parent.replaceChild(newNode, oldNode);
4657 }
4658
4659 newNode[jqLite.expando] = oldNode[jqLite.expando];
4660 $element[0] = newNode;
4661 }
4662 }];
4663 }
4664
4665 var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
4666 /**
4667 * Converts all accepted directives format into proper directive name.
4668 * All of these will become 'myDirective':
4669 * my:DiRective
4670 * my-directive
4671 * x-my-directive
4672 * data-my:directive
4673 *
4674 * Also there is special case for Moz prefix starting with upper case letter.
4675 * @param name Name to normalize
4676 */
4677 function directiveNormalize(name) {
4678 return camelCase(name.replace(PREFIX_REGEXP, ''));
4679 }
4680
4681 /**
4682 * @ngdoc object
4683 * @name ng.$compile.directive.Attributes
4684 * @description
4685 *
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:
4689 *
4690 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
4691 */
4692
4693 /**
4694 * @ngdoc property
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.
4699 */
4700
4701
4702 /**
4703 * @ngdoc function
4704 * @name ng.$compile.directive.Attributes#$set
4705 * @methodOf ng.$compile.directive.Attributes
4706 * @function
4707 *
4708 * @description
4709 * Set DOM element attribute value.
4710 *
4711 *
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.
4716 */
4717
4718
4719
4720 /**
4721 * Closure compiler type information
4722 */
4723
4724 function nodesetLinkingFn(
4725 /* angular.Scope */ scope,
4726 /* NodeList */ nodeList,
4727 /* Element */ rootElement,
4728 /* function(Function) */ boundTranscludeFn
4729 ){}
4730
4731 function directiveLinkingFn(
4732 /* nodesetLinkingFn */ nodesetLinkingFn,
4733 /* angular.Scope */ scope,
4734 /* Node */ node,
4735 /* Element */ rootElement,
4736 /* function(Function) */ boundTranscludeFn
4737 ){}
4738
4739 /**
4740 * @ngdoc object
4741 * @name ng.$controllerProvider
4742 * @description
4743 * The {@link ng.$controller $controller service} is used by Angular to create new
4744 * controllers.
4745 *
4746 * This provider allows controller registration via the
4747 * {@link ng.$controllerProvider#register register} method.
4748 */
4749 function $ControllerProvider() {
4750 var controllers = {};
4751
4752
4753 /**
4754 * @ngdoc function
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).
4760 */
4761 this.register = function(name, constructor) {
4762 if (isObject(name)) {
4763 extend(controllers, name)
4764 } else {
4765 controllers[name] = constructor;
4766 }
4767 };
4768
4769
4770 this.$get = ['$injector', '$window', function($injector, $window) {
4771
4772 /**
4773 * @ngdoc function
4774 * @name ng.$controller
4775 * @requires $injector
4776 *
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:
4780 *
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
4784 *
4785 * @param {Object} locals Injection locals for Controller.
4786 * @return {Object} Instance of given controller.
4787 *
4788 * @description
4789 * `$controller` service is responsible for instantiating controllers.
4790 *
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
4793 * BC version}.
4794 */
4795 return function(constructor, locals) {
4796 if(isString(constructor)) {
4797 var name = constructor;
4798 constructor = controllers.hasOwnProperty(name)
4799 ? controllers[name]
4800 : getter(locals.$scope, name, true) || getter($window, name, true);
4801
4802 assertArgFn(constructor, name, true);
4803 }
4804
4805 return $injector.instantiate(constructor, locals);
4806 };
4807 }];
4808 }
4809
4810 /**
4811 * @ngdoc object
4812 * @name ng.$document
4813 * @requires $window
4814 *
4815 * @description
4816 * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
4817 * element.
4818 */
4819 function $DocumentProvider(){
4820 this.$get = ['$window', function(window){
4821 return jqLite(window.document);
4822 }];
4823 }
4824
4825 /**
4826 * @ngdoc function
4827 * @name ng.$exceptionHandler
4828 * @requires $log
4829 *
4830 * @description
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.
4834 *
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.
4837 *
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.
4841 *
4842 */
4843 function $ExceptionHandlerProvider() {
4844 this.$get = ['$log', function($log) {
4845 return function(exception, cause) {
4846 $log.error.apply($log, arguments);
4847 };
4848 }];
4849 }
4850
4851 /**
4852 * @ngdoc object
4853 * @name ng.$interpolateProvider
4854 * @function
4855 *
4856 * @description
4857 *
4858 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
4859 */
4860 function $InterpolateProvider() {
4861 var startSymbol = '{{';
4862 var endSymbol = '}}';
4863
4864 /**
4865 * @ngdoc method
4866 * @name ng.$interpolateProvider#startSymbol
4867 * @methodOf ng.$interpolateProvider
4868 * @description
4869 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
4870 *
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.
4873 */
4874 this.startSymbol = function(value){
4875 if (value) {
4876 startSymbol = value;
4877 return this;
4878 } else {
4879 return startSymbol;
4880 }
4881 };
4882
4883 /**
4884 * @ngdoc method
4885 * @name ng.$interpolateProvider#endSymbol
4886 * @methodOf ng.$interpolateProvider
4887 * @description
4888 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
4889 *
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.
4892 */
4893 this.endSymbol = function(value){
4894 if (value) {
4895 endSymbol = value;
4896 return this;
4897 } else {
4898 return endSymbol;
4899 }
4900 };
4901
4902
4903 this.$get = ['$parse', function($parse) {
4904 var startSymbolLength = startSymbol.length,
4905 endSymbolLength = endSymbol.length;
4906
4907 /**
4908 * @ngdoc function
4909 * @name ng.$interpolate
4910 * @function
4911 *
4912 * @requires $parse
4913 *
4914 * @description
4915 *
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.
4920 *
4921 *
4922 <pre>
4923 var $interpolate = ...; // injected
4924 var exp = $interpolate('Hello {{name}}!');
4925 expect(exp({name:'Angular'}).toEqual('Hello Angular!');
4926 </pre>
4927 *
4928 *
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:
4935 *
4936 * * `context`: an object against which any expressions embedded in the strings are evaluated
4937 * against.
4938 *
4939 */
4940 function $interpolate(text, mustHaveExpression) {
4941 var startIndex,
4942 endIndex,
4943 index = 0,
4944 parts = [],
4945 length = text.length,
4946 hasInterpolation = false,
4947 fn,
4948 exp,
4949 concat = [];
4950
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)));
4956 fn.exp = exp;
4957 index = endIndex + endSymbolLength;
4958 hasInterpolation = true;
4959 } else {
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));
4962 index = length;
4963 }
4964 }
4965
4966 if (!(length = parts.length)) {
4967 // we added, nothing, must have been an empty string.
4968 parts.push('');
4969 length = 1;
4970 }
4971
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) {
4979 part = '';
4980 } else if (typeof part != 'string') {
4981 part = toJson(part);
4982 }
4983 }
4984 concat[i] = part;
4985 }
4986 return concat.join('');
4987 };
4988 fn.exp = text;
4989 fn.parts = parts;
4990 return fn;
4991 }
4992 }
4993
4994
4995 /**
4996 * @ngdoc method
4997 * @name ng.$interpolate#startSymbol
4998 * @methodOf ng.$interpolate
4999 * @description
5000 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
5001 *
5002 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
5003 * the symbol.
5004 *
5005 * @returns {string} start symbol.
5006 */
5007 $interpolate.startSymbol = function() {
5008 return startSymbol;
5009 }
5010
5011
5012 /**
5013 * @ngdoc method
5014 * @name ng.$interpolate#endSymbol
5015 * @methodOf ng.$interpolate
5016 * @description
5017 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
5018 *
5019 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
5020 * the symbol.
5021 *
5022 * @returns {string} start symbol.
5023 */
5024 $interpolate.endSymbol = function() {
5025 return endSymbol;
5026 }
5027
5028 return $interpolate;
5029 }];
5030 }
5031
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};
5036
5037
5038 /**
5039 * Encode path using encodeUriSegment, ignoring forward slashes
5040 *
5041 * @param {string} path Path to encode
5042 * @returns {string}
5043 */
5044 function encodePath(path) {
5045 var segments = path.split('/'),
5046 i = segments.length;
5047
5048 while (i--) {
5049 segments[i] = encodeUriSegment(segments[i]);
5050 }
5051
5052 return segments.join('/');
5053 }
5054
5055 function stripHash(url) {
5056 return url.split('#')[0];
5057 }
5058
5059
5060 function matchUrl(url, obj) {
5061 var match = URL_MATCH.exec(url);
5062
5063 match = {
5064 protocol: match[1],
5065 host: match[3],
5066 port: int(match[5]) || DEFAULT_PORTS[match[1]] || null,
5067 path: match[6] || '/',
5068 search: match[8],
5069 hash: match[10]
5070 };
5071
5072 if (obj) {
5073 obj.$$protocol = match.protocol;
5074 obj.$$host = match.host;
5075 obj.$$port = match.port;
5076 }
5077
5078 return match;
5079 }
5080
5081
5082 function composeProtocolHostPort(protocol, host, port) {
5083 return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port);
5084 }
5085
5086
5087 function pathPrefixFromBase(basePath) {
5088 return basePath.substr(0, basePath.lastIndexOf('/'));
5089 }
5090
5091
5092 function convertToHtml5Url(url, basePath, hashPrefix) {
5093 var match = matchUrl(url);
5094
5095 // already html5 url
5096 if (decodeURIComponent(match.path) != basePath || isUndefined(match.hash) ||
5097 match.hash.indexOf(hashPrefix) !== 0) {
5098 return url;
5099 // convert hashbang url -> html5 url
5100 } else {
5101 return composeProtocolHostPort(match.protocol, match.host, match.port) +
5102 pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
5103 }
5104 }
5105
5106
5107 function convertToHashbangUrl(url, basePath, hashPrefix) {
5108 var match = matchUrl(url);
5109
5110 // already hashbang url
5111 if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) &&
5112 match.hash.indexOf(hashPrefix) === 0) {
5113 return url;
5114 // convert html5 url -> hashbang url
5115 } else {
5116 var search = match.search && '?' + match.search || '',
5117 hash = match.hash && '#' + match.hash || '',
5118 pathPrefix = pathPrefixFromBase(basePath),
5119 path = match.path.substr(pathPrefix.length);
5120
5121 if (match.path.indexOf(pathPrefix) !== 0) {
5122 throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
5123 }
5124
5125 return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
5126 '#' + hashPrefix + path + search + hash;
5127 }
5128 }
5129
5130
5131 /**
5132 * LocationUrl represents an url
5133 * This object is exposed as $location service when HTML5 mode is enabled and supported
5134 *
5135 * @constructor
5136 * @param {string} url HTML5 url
5137 * @param {string} pathPrefix
5138 */
5139 function LocationUrl(url, pathPrefix, appBaseUrl) {
5140 pathPrefix = pathPrefix || '';
5141
5142 /**
5143 * Parse given html5 (regular) url string into properties
5144 * @param {string} newAbsoluteUrl HTML5 url
5145 * @private
5146 */
5147 this.$$parse = function(newAbsoluteUrl) {
5148 var match = matchUrl(newAbsoluteUrl, this);
5149
5150 if (match.path.indexOf(pathPrefix) !== 0) {
5151 throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
5152 }
5153
5154 this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
5155 this.$$search = parseKeyValue(match.search);
5156 this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
5157
5158 this.$$compose();
5159 };
5160
5161 /**
5162 * Compose url and update `absUrl` property
5163 * @private
5164 */
5165 this.$$compose = function() {
5166 var search = toKeyValue(this.$$search),
5167 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
5168
5169 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
5170 this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
5171 pathPrefix + this.$$url;
5172 };
5173
5174
5175 this.$$rewriteAppUrl = function(absoluteLinkUrl) {
5176 if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
5177 return absoluteLinkUrl;
5178 }
5179 }
5180
5181
5182 this.$$parse(url);
5183 }
5184
5185
5186 /**
5187 * LocationHashbangUrl represents url
5188 * This object is exposed as $location service when html5 history api is disabled or not supported
5189 *
5190 * @constructor
5191 * @param {string} url Legacy url
5192 * @param {string} hashPrefix Prefix for hash part (containing path and search)
5193 */
5194 function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
5195 var basePath;
5196
5197 /**
5198 * Parse given hashbang url into properties
5199 * @param {string} url Hashbang url
5200 * @private
5201 */
5202 this.$$parse = function(url) {
5203 var match = matchUrl(url, this);
5204
5205
5206 if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
5207 throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
5208 }
5209
5210 basePath = match.path + (match.search ? '?' + match.search : '');
5211 match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
5212 if (match[1]) {
5213 this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
5214 } else {
5215 this.$$path = '';
5216 }
5217
5218 this.$$search = parseKeyValue(match[3]);
5219 this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
5220
5221 this.$$compose();
5222 };
5223
5224 /**
5225 * Compose hashbang url and update `absUrl` property
5226 * @private
5227 */
5228 this.$$compose = function() {
5229 var search = toKeyValue(this.$$search),
5230 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
5231
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 : '');
5235 };
5236
5237 this.$$rewriteAppUrl = function(absoluteLinkUrl) {
5238 if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
5239 return absoluteLinkUrl;
5240 }
5241 }
5242
5243
5244 this.$$parse(url);
5245 }
5246
5247
5248 LocationUrl.prototype = {
5249
5250 /**
5251 * Has any change been replacing ?
5252 * @private
5253 */
5254 $$replace: false,
5255
5256 /**
5257 * @ngdoc method
5258 * @name ng.$location#absUrl
5259 * @methodOf ng.$location
5260 *
5261 * @description
5262 * This method is getter only.
5263 *
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}.
5266 *
5267 * @return {string} full url
5268 */
5269 absUrl: locationGetter('$$absUrl'),
5270
5271 /**
5272 * @ngdoc method
5273 * @name ng.$location#url
5274 * @methodOf ng.$location
5275 *
5276 * @description
5277 * This method is getter / setter.
5278 *
5279 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
5280 *
5281 * Change path, search and hash, when called with parameter and return `$location`.
5282 *
5283 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
5284 * @return {string} url
5285 */
5286 url: function(url, replace) {
5287 if (isUndefined(url))
5288 return this.$$url;
5289
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);
5294
5295 return this;
5296 },
5297
5298 /**
5299 * @ngdoc method
5300 * @name ng.$location#protocol
5301 * @methodOf ng.$location
5302 *
5303 * @description
5304 * This method is getter only.
5305 *
5306 * Return protocol of current url.
5307 *
5308 * @return {string} protocol of current url
5309 */
5310 protocol: locationGetter('$$protocol'),
5311
5312 /**
5313 * @ngdoc method
5314 * @name ng.$location#host
5315 * @methodOf ng.$location
5316 *
5317 * @description
5318 * This method is getter only.
5319 *
5320 * Return host of current url.
5321 *
5322 * @return {string} host of current url.
5323 */
5324 host: locationGetter('$$host'),
5325
5326 /**
5327 * @ngdoc method
5328 * @name ng.$location#port
5329 * @methodOf ng.$location
5330 *
5331 * @description
5332 * This method is getter only.
5333 *
5334 * Return port of current url.
5335 *
5336 * @return {Number} port
5337 */
5338 port: locationGetter('$$port'),
5339
5340 /**
5341 * @ngdoc method
5342 * @name ng.$location#path
5343 * @methodOf ng.$location
5344 *
5345 * @description
5346 * This method is getter / setter.
5347 *
5348 * Return path of current url when called without any parameter.
5349 *
5350 * Change path when called with parameter and return `$location`.
5351 *
5352 * Note: Path should always begin with forward slash (/), this method will add the forward slash
5353 * if it is missing.
5354 *
5355 * @param {string=} path New path
5356 * @return {string} path
5357 */
5358 path: locationGetterSetter('$$path', function(path) {
5359 return path.charAt(0) == '/' ? path : '/' + path;
5360 }),
5361
5362 /**
5363 * @ngdoc method
5364 * @name ng.$location#search
5365 * @methodOf ng.$location
5366 *
5367 * @description
5368 * This method is getter / setter.
5369 *
5370 * Return search part (as object) of current url when called without any parameter.
5371 *
5372 * Change search part when called with parameter and return `$location`.
5373 *
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.
5377 *
5378 * @return {string} search
5379 */
5380 search: function(search, paramValue) {
5381 if (isUndefined(search))
5382 return this.$$search;
5383
5384 if (isDefined(paramValue)) {
5385 if (paramValue === null) {
5386 delete this.$$search[search];
5387 } else {
5388 this.$$search[search] = paramValue;
5389 }
5390 } else {
5391 this.$$search = isString(search) ? parseKeyValue(search) : search;
5392 }
5393
5394 this.$$compose();
5395 return this;
5396 },
5397
5398 /**
5399 * @ngdoc method
5400 * @name ng.$location#hash
5401 * @methodOf ng.$location
5402 *
5403 * @description
5404 * This method is getter / setter.
5405 *
5406 * Return hash fragment when called without any parameter.
5407 *
5408 * Change hash fragment when called with parameter and return `$location`.
5409 *
5410 * @param {string=} hash New hash fragment
5411 * @return {string} hash
5412 */
5413 hash: locationGetterSetter('$$hash', identity),
5414
5415 /**
5416 * @ngdoc method
5417 * @name ng.$location#replace
5418 * @methodOf ng.$location
5419 *
5420 * @description
5421 * If called, all changes to $location during current `$digest` will be replacing current history
5422 * record, instead of adding new one.
5423 */
5424 replace: function() {
5425 this.$$replace = true;
5426 return this;
5427 }
5428 };
5429
5430 LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
5431
5432 function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
5433 LocationHashbangUrl.apply(this, arguments);
5434
5435
5436 this.$$rewriteAppUrl = function(absoluteLinkUrl) {
5437 if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
5438 return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
5439 }
5440 }
5441 }
5442
5443 LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
5444
5445 function locationGetter(property) {
5446 return function() {
5447 return this[property];
5448 };
5449 }
5450
5451
5452 function locationGetterSetter(property, preprocess) {
5453 return function(value) {
5454 if (isUndefined(value))
5455 return this[property];
5456
5457 this[property] = preprocess(value);
5458 this.$$compose();
5459
5460 return this;
5461 };
5462 }
5463
5464
5465 /**
5466 * @ngdoc object
5467 * @name ng.$location
5468 *
5469 * @requires $browser
5470 * @requires $sniffer
5471 * @requires $rootElement
5472 *
5473 * @description
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.
5478 *
5479 * **The $location service:**
5480 *
5481 * - Exposes the current URL in the browser address bar, so you can
5482 * - Watch and observe the URL.
5483 * - Change 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).
5489 *
5490 * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
5491 * Services: Using $location}
5492 */
5493
5494 /**
5495 * @ngdoc object
5496 * @name ng.$locationProvider
5497 * @description
5498 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
5499 */
5500 function $LocationProvider(){
5501 var hashPrefix = '',
5502 html5Mode = false;
5503
5504 /**
5505 * @ngdoc property
5506 * @name ng.$locationProvider#hashPrefix
5507 * @methodOf ng.$locationProvider
5508 * @description
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
5511 */
5512 this.hashPrefix = function(prefix) {
5513 if (isDefined(prefix)) {
5514 hashPrefix = prefix;
5515 return this;
5516 } else {
5517 return hashPrefix;
5518 }
5519 };
5520
5521 /**
5522 * @ngdoc property
5523 * @name ng.$locationProvider#html5Mode
5524 * @methodOf ng.$locationProvider
5525 * @description
5526 * @param {string=} mode Use HTML5 strategy if available.
5527 * @returns {*} current value if used as getter or itself (chaining) if used as setter
5528 */
5529 this.html5Mode = function(mode) {
5530 if (isDefined(mode)) {
5531 html5Mode = mode;
5532 return this;
5533 } else {
5534 return html5Mode;
5535 }
5536 };
5537
5538 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
5539 function( $rootScope, $browser, $sniffer, $rootElement) {
5540 var $location,
5541 basePath,
5542 pathPrefix,
5543 initUrl = $browser.url(),
5544 initUrlParts = matchUrl(initUrl),
5545 appBaseUrl;
5546
5547 if (html5Mode) {
5548 basePath = $browser.baseHref() || '/';
5549 pathPrefix = pathPrefixFromBase(basePath);
5550 appBaseUrl =
5551 composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
5552 pathPrefix + '/';
5553
5554 if ($sniffer.history) {
5555 $location = new LocationUrl(
5556 convertToHtml5Url(initUrl, basePath, hashPrefix),
5557 pathPrefix, appBaseUrl);
5558 } else {
5559 $location = new LocationHashbangInHtml5Url(
5560 convertToHashbangUrl(initUrl, basePath, hashPrefix),
5561 hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
5562 }
5563 } else {
5564 appBaseUrl =
5565 composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
5566 (initUrlParts.path || '') +
5567 (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
5568 '#' + hashPrefix + '/';
5569
5570 $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
5571 }
5572
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
5576
5577 if (event.ctrlKey || event.metaKey || event.which == 2) return;
5578
5579 var elm = jqLite(event.target);
5580
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;
5585 }
5586
5587 var absHref = elm.prop('href'),
5588 rewrittenUrl = $location.$$rewriteAppUrl(absHref);
5589
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;
5597 }
5598 });
5599
5600
5601 // rewrite hashbang url <> html5 url
5602 if ($location.absUrl() != initUrl) {
5603 $browser.url($location.absUrl(), true);
5604 }
5605
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());
5611 return;
5612 }
5613 $rootScope.$evalAsync(function() {
5614 var oldUrl = $location.absUrl();
5615
5616 $location.$$parse(newUrl);
5617 afterLocationChange(oldUrl);
5618 });
5619 if (!$rootScope.$$phase) $rootScope.$digest();
5620 }
5621 });
5622
5623 // update browser
5624 var changeCounter = 0;
5625 $rootScope.$watch(function $locationWatch() {
5626 var oldUrl = $browser.url();
5627 var currentReplace = $location.$$replace;
5628
5629 if (!changeCounter || oldUrl != $location.absUrl()) {
5630 changeCounter++;
5631 $rootScope.$evalAsync(function() {
5632 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
5633 defaultPrevented) {
5634 $location.$$parse(oldUrl);
5635 } else {
5636 $browser.url($location.absUrl(), currentReplace);
5637 afterLocationChange(oldUrl);
5638 }
5639 });
5640 }
5641 $location.$$replace = false;
5642
5643 return changeCounter;
5644 });
5645
5646 return $location;
5647
5648 function afterLocationChange(oldUrl) {
5649 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
5650 }
5651 }];
5652 }
5653
5654 /**
5655 * @ngdoc object
5656 * @name ng.$log
5657 * @requires $window
5658 *
5659 * @description
5660 * Simple service for logging. Default implementation writes the message
5661 * into the browser's console (if present).
5662 *
5663 * The main purpose of this service is to simplify debugging and troubleshooting.
5664 *
5665 * @example
5666 <example>
5667 <file name="script.js">
5668 function LogCtrl($scope, $log) {
5669 $scope.$log = $log;
5670 $scope.message = 'Hello World!';
5671 }
5672 </file>
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>
5676 Message:
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>
5682 </div>
5683 </file>
5684 </example>
5685 */
5686
5687 function $LogProvider(){
5688 this.$get = ['$window', function($window){
5689 return {
5690 /**
5691 * @ngdoc method
5692 * @name ng.$log#log
5693 * @methodOf ng.$log
5694 *
5695 * @description
5696 * Write a log message
5697 */
5698 log: consoleLog('log'),
5699
5700 /**
5701 * @ngdoc method
5702 * @name ng.$log#warn
5703 * @methodOf ng.$log
5704 *
5705 * @description
5706 * Write a warning message
5707 */
5708 warn: consoleLog('warn'),
5709
5710 /**
5711 * @ngdoc method
5712 * @name ng.$log#info
5713 * @methodOf ng.$log
5714 *
5715 * @description
5716 * Write an information message
5717 */
5718 info: consoleLog('info'),
5719
5720 /**
5721 * @ngdoc method
5722 * @name ng.$log#error
5723 * @methodOf ng.$log
5724 *
5725 * @description
5726 * Write an error message
5727 */
5728 error: consoleLog('error')
5729 };
5730
5731 function formatError(arg) {
5732 if (arg instanceof Error) {
5733 if (arg.stack) {
5734 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
5735 ? 'Error: ' + arg.message + '\n' + arg.stack
5736 : arg.stack;
5737 } else if (arg.sourceURL) {
5738 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
5739 }
5740 }
5741 return arg;
5742 }
5743
5744 function consoleLog(type) {
5745 var console = $window.console || {},
5746 logFn = console[type] || console.log || noop;
5747
5748 if (logFn.apply) {
5749 return function() {
5750 var args = [];
5751 forEach(arguments, function(arg) {
5752 args.push(formatError(arg));
5753 });
5754 return logFn.apply(console, args);
5755 };
5756 }
5757
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) {
5761 logFn(arg1, arg2);
5762 }
5763 }
5764 }];
5765 }
5766
5767 var OPERATORS = {
5768 'null':function(){return null;},
5769 'true':function(){return true;},
5770 'false':function(){return false;},
5771 undefined:noop,
5772 '+':function(self, locals, a,b){
5773 a=a(self, locals); b=b(self, locals);
5774 if (isDefined(a)) {
5775 if (isDefined(b)) {
5776 return a + b;
5777 }
5778 return a;
5779 }
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);},
5786 '=':noop,
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);}
5799 };
5800 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
5801
5802 function lex(text, csp){
5803 var tokens = [],
5804 token,
5805 index = 0,
5806 json = [],
5807 ch,
5808 lastCh = ':'; // can start regexp
5809
5810 while (index < text.length) {
5811 ch = text.charAt(index);
5812 if (is('"\'')) {
5813 readString(ch);
5814 } else if (isNumber(ch) || is('.') && isNumber(peek())) {
5815 readNumber();
5816 } else if (isIdent(ch)) {
5817 readIdent();
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;
5822 }
5823 } else if (is('(){}[].,;:')) {
5824 tokens.push({
5825 index:index,
5826 text:ch,
5827 json:(was(':[,') && is('{[')) || is('}]:,')
5828 });
5829 if (is('{[')) json.unshift(ch);
5830 if (is('}]')) json.shift();
5831 index++;
5832 } else if (isWhitespace(ch)) {
5833 index++;
5834 continue;
5835 } else {
5836 var ch2 = ch + peek(),
5837 fn = OPERATORS[ch],
5838 fn2 = OPERATORS[ch2];
5839 if (fn2) {
5840 tokens.push({index:index, text:ch2, fn:fn2});
5841 index += 2;
5842 } else if (fn) {
5843 tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
5844 index += 1;
5845 } else {
5846 throwError("Unexpected next character ", index, index+1);
5847 }
5848 }
5849 lastCh = ch;
5850 }
5851 return tokens;
5852
5853 function is(chars) {
5854 return chars.indexOf(ch) != -1;
5855 }
5856
5857 function was(chars) {
5858 return chars.indexOf(lastCh) != -1;
5859 }
5860
5861 function peek() {
5862 return index + 1 < text.length ? text.charAt(index + 1) : false;
5863 }
5864 function isNumber(ch) {
5865 return '0' <= ch && ch <= '9';
5866 }
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
5870 }
5871 function isIdent(ch) {
5872 return 'a' <= ch && ch <= 'z' ||
5873 'A' <= ch && ch <= 'Z' ||
5874 '_' == ch || ch == '$';
5875 }
5876 function isExpOperator(ch) {
5877 return ch == '-' || ch == '+' || isNumber(ch);
5878 }
5879
5880 function throwError(error, start, end) {
5881 end = end || index;
5882 throw Error("Lexer Error: " + error + " at column" +
5883 (isDefined(start)
5884 ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
5885 : " " + end) +
5886 " in expression [" + text + "].");
5887 }
5888
5889 function readNumber() {
5890 var number = "";
5891 var start = index;
5892 while (index < text.length) {
5893 var ch = lowercase(text.charAt(index));
5894 if (ch == '.' || isNumber(ch)) {
5895 number += ch;
5896 } else {
5897 var peekCh = peek();
5898 if (ch == 'e' && isExpOperator(peekCh)) {
5899 number += ch;
5900 } else if (isExpOperator(ch) &&
5901 peekCh && isNumber(peekCh) &&
5902 number.charAt(number.length - 1) == 'e') {
5903 number += ch;
5904 } else if (isExpOperator(ch) &&
5905 (!peekCh || !isNumber(peekCh)) &&
5906 number.charAt(number.length - 1) == 'e') {
5907 throwError('Invalid exponent');
5908 } else {
5909 break;
5910 }
5911 }
5912 index++;
5913 }
5914 number = 1 * number;
5915 tokens.push({index:start, text:number, json:true,
5916 fn:function() {return number;}});
5917 }
5918 function readIdent() {
5919 var ident = "",
5920 start = index,
5921 lastDot, peekIndex, methodName, ch;
5922
5923 while (index < text.length) {
5924 ch = text.charAt(index);
5925 if (ch == '.' || isIdent(ch) || isNumber(ch)) {
5926 if (ch == '.') lastDot = index;
5927 ident += ch;
5928 } else {
5929 break;
5930 }
5931 index++;
5932 }
5933
5934 //check if this is not a method invocation and if it is back out to last dot
5935 if (lastDot) {
5936 peekIndex = index;
5937 while(peekIndex < text.length) {
5938 ch = text.charAt(peekIndex);
5939 if (ch == '(') {
5940 methodName = ident.substr(lastDot - start + 1);
5941 ident = ident.substr(0, lastDot - start);
5942 index = peekIndex;
5943 break;
5944 }
5945 if(isWhitespace(ch)) {
5946 peekIndex++;
5947 } else {
5948 break;
5949 }
5950 }
5951 }
5952
5953
5954 var token = {
5955 index:start,
5956 text:ident
5957 };
5958
5959 if (OPERATORS.hasOwnProperty(ident)) {
5960 token.fn = token.json = OPERATORS[ident];
5961 } else {
5962 var getter = getterFn(ident, csp);
5963 token.fn = extend(function(self, locals) {
5964 return (getter(self, locals));
5965 }, {
5966 assign: function(self, value) {
5967 return setter(self, ident, value);
5968 }
5969 });
5970 }
5971
5972 tokens.push(token);
5973
5974 if (methodName) {
5975 tokens.push({
5976 index:lastDot,
5977 text: '.',
5978 json: false
5979 });
5980 tokens.push({
5981 index: lastDot + 1,
5982 text: methodName,
5983 json: false
5984 });
5985 }
5986 }
5987
5988 function readString(quote) {
5989 var start = index;
5990 index++;
5991 var string = "";
5992 var rawString = quote;
5993 var escape = false;
5994 while (index < text.length) {
5995 var ch = text.charAt(index);
5996 rawString += ch;
5997 if (escape) {
5998 if (ch == 'u') {
5999 var hex = text.substring(index + 1, index + 5);
6000 if (!hex.match(/[\da-f]{4}/i))
6001 throwError( "Invalid unicode escape [\\u" + hex + "]");
6002 index += 4;
6003 string += String.fromCharCode(parseInt(hex, 16));
6004 } else {
6005 var rep = ESCAPE[ch];
6006 if (rep) {
6007 string += rep;
6008 } else {
6009 string += ch;
6010 }
6011 }
6012 escape = false;
6013 } else if (ch == '\\') {
6014 escape = true;
6015 } else if (ch == quote) {
6016 index++;
6017 tokens.push({
6018 index:start,
6019 text:rawString,
6020 string:string,
6021 json:true,
6022 fn:function() { return string; }
6023 });
6024 return;
6025 } else {
6026 string += ch;
6027 }
6028 index++;
6029 }
6030 throwError("Unterminated quote", start);
6031 }
6032 }
6033
6034 /////////////////////////////////////////
6035
6036 function parser(text, json, $filter, csp){
6037 var ZERO = valueFn(0),
6038 value,
6039 tokens = lex(text, csp),
6040 assignment = _assignment,
6041 functionCall = _functionCall,
6042 fieldAccess = _fieldAccess,
6043 objectIndex = _objectIndex,
6044 filterChain = _filterChain;
6045
6046 if(json){
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;
6050 functionCall =
6051 fieldAccess =
6052 objectIndex =
6053 filterChain =
6054 function() { throwError("is not valid json", {text:text, index:0}); };
6055 value = primary();
6056 } else {
6057 value = statements();
6058 }
6059 if (tokens.length !== 0) {
6060 throwError("is an unexpected token", tokens[0]);
6061 }
6062 return value;
6063
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) + "].");
6070 }
6071
6072 function peekToken() {
6073 if (tokens.length === 0)
6074 throw Error("Unexpected end of expression: " + text);
6075 return tokens[0];
6076 }
6077
6078 function peek(e1, e2, e3, e4) {
6079 if (tokens.length > 0) {
6080 var token = tokens[0];
6081 var t = token.text;
6082 if (t==e1 || t==e2 || t==e3 || t==e4 ||
6083 (!e1 && !e2 && !e3 && !e4)) {
6084 return token;
6085 }
6086 }
6087 return false;
6088 }
6089
6090 function expect(e1, e2, e3, e4){
6091 var token = peek(e1, e2, e3, e4);
6092 if (token) {
6093 if (json && !token.json) {
6094 throwError("is not valid json", token);
6095 }
6096 tokens.shift();
6097 return token;
6098 }
6099 return false;
6100 }
6101
6102 function consume(e1){
6103 if (!expect(e1)) {
6104 throwError("is unexpected, expecting [" + e1 + "]", peek());
6105 }
6106 }
6107
6108 function unaryFn(fn, right) {
6109 return function(self, locals) {
6110 return fn(self, locals, right);
6111 };
6112 }
6113
6114 function binaryFn(left, fn, right) {
6115 return function(self, locals) {
6116 return fn(self, locals, left, right);
6117 };
6118 }
6119
6120 function statements() {
6121 var statements = [];
6122 while(true) {
6123 if (tokens.length > 0 && !peek('}', ')', ';', ']'))
6124 statements.push(filterChain());
6125 if (!expect(';')) {
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
6129 ? statements[0]
6130 : function(self, locals){
6131 var value;
6132 for ( var i = 0; i < statements.length; i++) {
6133 var statement = statements[i];
6134 if (statement)
6135 value = statement(self, locals);
6136 }
6137 return value;
6138 };
6139 }
6140 }
6141 }
6142
6143 function _filterChain() {
6144 var left = expression();
6145 var token;
6146 while(true) {
6147 if ((token = expect('|'))) {
6148 left = binaryFn(left, token.fn, filter());
6149 } else {
6150 return left;
6151 }
6152 }
6153 }
6154
6155 function filter() {
6156 var token = expect();
6157 var fn = $filter(token.text);
6158 var argsFn = [];
6159 while(true) {
6160 if ((token = expect(':'))) {
6161 argsFn.push(expression());
6162 } else {
6163 var fnInvoke = function(self, locals, input){
6164 var args = [input];
6165 for ( var i = 0; i < argsFn.length; i++) {
6166 args.push(argsFn[i](self, locals));
6167 }
6168 return fn.apply(self, args);
6169 };
6170 return function() {
6171 return fnInvoke;
6172 };
6173 }
6174 }
6175 }
6176
6177 function expression() {
6178 return assignment();
6179 }
6180
6181 function _assignment() {
6182 var left = logicalOR();
6183 var right;
6184 var token;
6185 if ((token = expect('='))) {
6186 if (!left.assign) {
6187 throwError("implies assignment but [" +
6188 text.substring(0, token.index) + "] can not be assigned to", token);
6189 }
6190 right = logicalOR();
6191 return function(scope, locals){
6192 return left.assign(scope, right(scope, locals), locals);
6193 };
6194 } else {
6195 return left;
6196 }
6197 }
6198
6199 function logicalOR() {
6200 var left = logicalAND();
6201 var token;
6202 while(true) {
6203 if ((token = expect('||'))) {
6204 left = binaryFn(left, token.fn, logicalAND());
6205 } else {
6206 return left;
6207 }
6208 }
6209 }
6210
6211 function logicalAND() {
6212 var left = equality();
6213 var token;
6214 if ((token = expect('&&'))) {
6215 left = binaryFn(left, token.fn, logicalAND());
6216 }
6217 return left;
6218 }
6219
6220 function equality() {
6221 var left = relational();
6222 var token;
6223 if ((token = expect('==','!='))) {
6224 left = binaryFn(left, token.fn, equality());
6225 }
6226 return left;
6227 }
6228
6229 function relational() {
6230 var left = additive();
6231 var token;
6232 if ((token = expect('<', '>', '<=', '>='))) {
6233 left = binaryFn(left, token.fn, relational());
6234 }
6235 return left;
6236 }
6237
6238 function additive() {
6239 var left = multiplicative();
6240 var token;
6241 while ((token = expect('+','-'))) {
6242 left = binaryFn(left, token.fn, multiplicative());
6243 }
6244 return left;
6245 }
6246
6247 function multiplicative() {
6248 var left = unary();
6249 var token;
6250 while ((token = expect('*','/','%'))) {
6251 left = binaryFn(left, token.fn, unary());
6252 }
6253 return left;
6254 }
6255
6256 function unary() {
6257 var token;
6258 if (expect('+')) {
6259 return primary();
6260 } else if ((token = expect('-'))) {
6261 return binaryFn(ZERO, token.fn, unary());
6262 } else if ((token = expect('!'))) {
6263 return unaryFn(token.fn, unary());
6264 } else {
6265 return primary();
6266 }
6267 }
6268
6269
6270 function primary() {
6271 var primary;
6272 if (expect('(')) {
6273 primary = filterChain();
6274 consume(')');
6275 } else if (expect('[')) {
6276 primary = arrayDeclaration();
6277 } else if (expect('{')) {
6278 primary = object();
6279 } else {
6280 var token = expect();
6281 primary = token.fn;
6282 if (!primary) {
6283 throwError("not a primary expression", token);
6284 }
6285 }
6286
6287 var next, context;
6288 while ((next = expect('(', '[', '.'))) {
6289 if (next.text === '(') {
6290 primary = functionCall(primary, context);
6291 context = null;
6292 } else if (next.text === '[') {
6293 context = primary;
6294 primary = objectIndex(primary);
6295 } else if (next.text === '.') {
6296 context = primary;
6297 primary = fieldAccess(primary);
6298 } else {
6299 throwError("IMPOSSIBLE");
6300 }
6301 }
6302 return primary;
6303 }
6304
6305 function _fieldAccess(object) {
6306 var field = expect().text;
6307 var getter = getterFn(field, csp);
6308 return extend(
6309 function(scope, locals, self) {
6310 return getter(self || object(scope, locals), locals);
6311 },
6312 {
6313 assign:function(scope, value, locals) {
6314 return setter(object(scope, locals), field, value);
6315 }
6316 }
6317 );
6318 }
6319
6320 function _objectIndex(obj) {
6321 var indexFn = expression();
6322 consume(']');
6323 return extend(
6324 function(self, locals){
6325 var o = obj(self, locals),
6326 i = indexFn(self, locals),
6327 v, p;
6328
6329 if (!o) return undefined;
6330 v = o[i];
6331 if (v && v.then) {
6332 p = v;
6333 if (!('$$v' in v)) {
6334 p.$$v = undefined;
6335 p.then(function(val) { p.$$v = val; });
6336 }
6337 v = v.$$v;
6338 }
6339 return v;
6340 }, {
6341 assign:function(self, value, locals){
6342 return obj(self, locals)[indexFn(self, locals)] = value;
6343 }
6344 });
6345 }
6346
6347 function _functionCall(fn, contextGetter) {
6348 var argsFn = [];
6349 if (peekToken().text != ')') {
6350 do {
6351 argsFn.push(expression());
6352 } while (expect(','));
6353 }
6354 consume(')');
6355 return function(scope, locals){
6356 var args = [],
6357 context = contextGetter ? contextGetter(scope, locals) : scope;
6358
6359 for ( var i = 0; i < argsFn.length; i++) {
6360 args.push(argsFn[i](scope, locals));
6361 }
6362 var fnPtr = fn(scope, locals, context) || noop;
6363 // IE stupidity!
6364 return fnPtr.apply
6365 ? fnPtr.apply(context, args)
6366 : fnPtr(args[0], args[1], args[2], args[3], args[4]);
6367 };
6368 }
6369
6370 // This is used with json array declaration
6371 function arrayDeclaration () {
6372 var elementFns = [];
6373 if (peekToken().text != ']') {
6374 do {
6375 elementFns.push(expression());
6376 } while (expect(','));
6377 }
6378 consume(']');
6379 return function(self, locals){
6380 var array = [];
6381 for ( var i = 0; i < elementFns.length; i++) {
6382 array.push(elementFns[i](self, locals));
6383 }
6384 return array;
6385 };
6386 }
6387
6388 function object () {
6389 var keyValues = [];
6390 if (peekToken().text != '}') {
6391 do {
6392 var token = expect(),
6393 key = token.string || token.text;
6394 consume(":");
6395 var value = expression();
6396 keyValues.push({key:key, value:value});
6397 } while (expect(','));
6398 }
6399 consume('}');
6400 return function(self, locals){
6401 var object = {};
6402 for ( var i = 0; i < keyValues.length; i++) {
6403 var keyValue = keyValues[i];
6404 object[keyValue.key] = keyValue.value(self, locals);
6405 }
6406 return object;
6407 };
6408 }
6409 }
6410
6411 //////////////////////////////////////////////////
6412 // Parser helper functions
6413 //////////////////////////////////////////////////
6414
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];
6420 if (!propertyObj) {
6421 propertyObj = {};
6422 obj[key] = propertyObj;
6423 }
6424 obj = propertyObj;
6425 }
6426 obj[element.shift()] = setValue;
6427 return setValue;
6428 }
6429
6430 /**
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
6436 */
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('.');
6441 var key;
6442 var lastInstance = obj;
6443 var len = keys.length;
6444
6445 for (var i = 0; i < len; i++) {
6446 key = keys[i];
6447 if (obj) {
6448 obj = (lastInstance = obj)[key];
6449 }
6450 }
6451 if (!bindFnToScope && isFunction(obj)) {
6452 return bind(lastInstance, obj);
6453 }
6454 return obj;
6455 }
6456
6457 var getterFnCache = {};
6458
6459 /**
6460 * Implementation of the "Black Hole" variant from:
6461 * - http://jsperf.com/angularjs-parse-getter/4
6462 * - http://jsperf.com/path-evaluation-simplified/7
6463 */
6464 function cspSafeGetterFn(key0, key1, key2, key3, key4) {
6465 return function(scope, locals) {
6466 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
6467 promise;
6468
6469 if (pathVal === null || pathVal === undefined) return pathVal;
6470
6471 pathVal = pathVal[key0];
6472 if (pathVal && pathVal.then) {
6473 if (!("$$v" in pathVal)) {
6474 promise = pathVal;
6475 promise.$$v = undefined;
6476 promise.then(function(val) { promise.$$v = val; });
6477 }
6478 pathVal = pathVal.$$v;
6479 }
6480 if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
6481
6482 pathVal = pathVal[key1];
6483 if (pathVal && pathVal.then) {
6484 if (!("$$v" in pathVal)) {
6485 promise = pathVal;
6486 promise.$$v = undefined;
6487 promise.then(function(val) { promise.$$v = val; });
6488 }
6489 pathVal = pathVal.$$v;
6490 }
6491 if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
6492
6493 pathVal = pathVal[key2];
6494 if (pathVal && pathVal.then) {
6495 if (!("$$v" in pathVal)) {
6496 promise = pathVal;
6497 promise.$$v = undefined;
6498 promise.then(function(val) { promise.$$v = val; });
6499 }
6500 pathVal = pathVal.$$v;
6501 }
6502 if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
6503
6504 pathVal = pathVal[key3];
6505 if (pathVal && pathVal.then) {
6506 if (!("$$v" in pathVal)) {
6507 promise = pathVal;
6508 promise.$$v = undefined;
6509 promise.then(function(val) { promise.$$v = val; });
6510 }
6511 pathVal = pathVal.$$v;
6512 }
6513 if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
6514
6515 pathVal = pathVal[key4];
6516 if (pathVal && pathVal.then) {
6517 if (!("$$v" in pathVal)) {
6518 promise = pathVal;
6519 promise.$$v = undefined;
6520 promise.then(function(val) { promise.$$v = val; });
6521 }
6522 pathVal = pathVal.$$v;
6523 }
6524 return pathVal;
6525 };
6526 }
6527
6528 function getterFn(path, csp) {
6529 if (getterFnCache.hasOwnProperty(path)) {
6530 return getterFnCache[path];
6531 }
6532
6533 var pathKeys = path.split('.'),
6534 pathKeysLength = pathKeys.length,
6535 fn;
6536
6537 if (csp) {
6538 fn = (pathKeysLength < 6)
6539 ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
6540 : function(scope, locals) {
6541 var i = 0, val;
6542 do {
6543 val = cspSafeGetterFn(
6544 pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
6545 )(scope, locals);
6546
6547 locals = undefined; // clear after first iteration
6548 scope = val;
6549 } while (i < pathKeysLength);
6550 return val;
6551 }
6552 } else {
6553 var code = 'var l, fn, p;\n';
6554 forEach(pathKeys, function(key, index) {
6555 code += 'if(s === null || s === undefined) return s;\n' +
6556 'l=s;\n' +
6557 's='+ (index
6558 // we simply dereference 's' on any .dot notation
6559 ? 's'
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' +
6564 ' p=s;\n' +
6565 ' p.$$v = undefined;\n' +
6566 ' p.then(function(v) {p.$$v=v;});\n' +
6567 '}\n' +
6568 ' s=s.$$v\n' +
6569 '}\n';
6570 });
6571 code += 'return s;';
6572 fn = Function('s', 'k', code); // s=scope, k=locals
6573 fn.toString = function() { return code; };
6574 }
6575
6576 return getterFnCache[path] = fn;
6577 }
6578
6579 ///////////////////////////////////
6580
6581 /**
6582 * @ngdoc function
6583 * @name ng.$parse
6584 * @function
6585 *
6586 * @description
6587 *
6588 * Converts Angular {@link guide/expression expression} into a function.
6589 *
6590 * <pre>
6591 * var getter = $parse('user.name');
6592 * var setter = getter.assign;
6593 * var context = {user:{name:'angular'}};
6594 * var locals = {user:{name:'local'}};
6595 *
6596 * expect(getter(context)).toEqual('angular');
6597 * setter(context, 'newValue');
6598 * expect(context.user.name).toEqual('newValue');
6599 * expect(getter(context, locals)).toEqual('local');
6600 * </pre>
6601 *
6602 *
6603 * @param {string} expression String expression to compile.
6604 * @returns {function(context, locals)} a function which represents the compiled expression:
6605 *
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
6609 * `context`.
6610 *
6611 * The return function also has an `assign` property, if the expression is assignable, which
6612 * allows one to set values to expressions.
6613 *
6614 */
6615 function $ParseProvider() {
6616 var cache = {};
6617 this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
6618 return function(exp) {
6619 switch(typeof exp) {
6620 case 'string':
6621 return cache.hasOwnProperty(exp)
6622 ? cache[exp]
6623 : cache[exp] = parser(exp, false, $filter, $sniffer.csp);
6624 case 'function':
6625 return exp;
6626 default:
6627 return noop;
6628 }
6629 };
6630 }];
6631 }
6632
6633 /**
6634 * @ngdoc service
6635 * @name ng.$q
6636 * @requires $rootScope
6637 *
6638 * @description
6639 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
6640 *
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.
6644 *
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.
6647 *
6648 * <pre>
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).
6651 *
6652 * function asyncGreet(name) {
6653 * var deferred = $q.defer();
6654 *
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 + '!');
6661 * } else {
6662 * deferred.reject('Greeting ' + name + ' is not allowed.');
6663 * }
6664 * });
6665 * }, 1000);
6666 *
6667 * return deferred.promise;
6668 * }
6669 *
6670 * var promise = asyncGreet('Robin Hood');
6671 * promise.then(function(greeting) {
6672 * alert('Success: ' + greeting);
6673 * }, function(reason) {
6674 * alert('Failed: ' + reason);
6675 * });
6676 * </pre>
6677 *
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).
6681 *
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.
6686 *
6687 *
6688 * # The Deferred API
6689 *
6690 * A new instance of deferred is constructed by calling `$q.defer()`.
6691 *
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.
6694 *
6695 * **Methods**
6696 *
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`.
6701 *
6702 * **Properties**
6703 *
6704 * - promise – `{Promise}` – promise object associated with this deferred.
6705 *
6706 *
6707 * # The Promise API
6708 *
6709 * A new promise instance is created when a deferred instance is created and can be retrieved by
6710 * calling `deferred.promise`.
6711 *
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.
6714 *
6715 * **Methods**
6716 *
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.
6720 *
6721 * This method *returns a new promise* which is resolved or rejected via the return value of the
6722 * `successCallback` or `errorCallback`.
6723 *
6724 *
6725 * # Chaining promises
6726 *
6727 * Because calling `then` api of a promise returns a new derived promise, it is easily possible
6728 * to create a chain of promises:
6729 *
6730 * <pre>
6731 * promiseB = promiseA.then(function(result) {
6732 * return result + 1;
6733 * });
6734 *
6735 * // promiseB will be resolved immediately after promiseA is resolved and its value will be
6736 * // the result of promiseA incremented by 1
6737 * </pre>
6738 *
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.
6743 *
6744 *
6745 * # Differences between Kris Kowal's Q and $q
6746 *
6747 * There are three main differences:
6748 *
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.
6756 *
6757 * # Testing
6758 *
6759 * <pre>
6760 * it('should simulate promise', inject(function($q, $rootScope) {
6761 * var deferred = $q.defer();
6762 * var promise = deferred.promise;
6763 * var resolvedValue;
6764 *
6765 * promise.then(function(value) { resolvedValue = value; });
6766 * expect(resolvedValue).toBeUndefined();
6767 *
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();
6774 *
6775 * // Propagate promise resolution to 'then' functions using $apply().
6776 * $rootScope.$apply();
6777 * expect(resolvedValue).toEqual(123);
6778 * });
6779 * </pre>
6780 */
6781 function $QProvider() {
6782
6783 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
6784 return qFactory(function(callback) {
6785 $rootScope.$evalAsync(callback);
6786 }, $exceptionHandler);
6787 }];
6788 }
6789
6790
6791 /**
6792 * Constructs a promise manager.
6793 *
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.
6798 */
6799 function qFactory(nextTick, exceptionHandler) {
6800
6801 /**
6802 * @ngdoc
6803 * @name ng.$q#defer
6804 * @methodOf ng.$q
6805 * @description
6806 * Creates a `Deferred` object which represents a task which will finish in the future.
6807 *
6808 * @returns {Deferred} Returns a new instance of deferred.
6809 */
6810 var defer = function() {
6811 var pending = [],
6812 value, deferred;
6813
6814 deferred = {
6815
6816 resolve: function(val) {
6817 if (pending) {
6818 var callbacks = pending;
6819 pending = undefined;
6820 value = ref(val);
6821
6822 if (callbacks.length) {
6823 nextTick(function() {
6824 var callback;
6825 for (var i = 0, ii = callbacks.length; i < ii; i++) {
6826 callback = callbacks[i];
6827 value.then(callback[0], callback[1]);
6828 }
6829 });
6830 }
6831 }
6832 },
6833
6834
6835 reject: function(reason) {
6836 deferred.resolve(reject(reason));
6837 },
6838
6839
6840 promise: {
6841 then: function(callback, errback) {
6842 var result = defer();
6843
6844 var wrappedCallback = function(value) {
6845 try {
6846 result.resolve((callback || defaultCallback)(value));
6847 } catch(e) {
6848 exceptionHandler(e);
6849 result.reject(e);
6850 }
6851 };
6852
6853 var wrappedErrback = function(reason) {
6854 try {
6855 result.resolve((errback || defaultErrback)(reason));
6856 } catch(e) {
6857 exceptionHandler(e);
6858 result.reject(e);
6859 }
6860 };
6861
6862 if (pending) {
6863 pending.push([wrappedCallback, wrappedErrback]);
6864 } else {
6865 value.then(wrappedCallback, wrappedErrback);
6866 }
6867
6868 return result.promise;
6869 }
6870 }
6871 };
6872
6873 return deferred;
6874 };
6875
6876
6877 var ref = function(value) {
6878 if (value && value.then) return value;
6879 return {
6880 then: function(callback) {
6881 var result = defer();
6882 nextTick(function() {
6883 result.resolve(callback(value));
6884 });
6885 return result.promise;
6886 }
6887 };
6888 };
6889
6890
6891 /**
6892 * @ngdoc
6893 * @name ng.$q#reject
6894 * @methodOf ng.$q
6895 * @description
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.
6899 *
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
6904 * `reject`.
6905 *
6906 * <pre>
6907 * promiseB = promiseA.then(function(result) {
6908 * // success: do something and resolve promiseB
6909 * // with the old or a new result
6910 * return 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;
6918 * }
6919 * return $q.reject(reason);
6920 * });
6921 * </pre>
6922 *
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`.
6925 */
6926 var reject = function(reason) {
6927 return {
6928 then: function(callback, errback) {
6929 var result = defer();
6930 nextTick(function() {
6931 result.resolve((errback || defaultErrback)(reason));
6932 });
6933 return result.promise;
6934 }
6935 };
6936 };
6937
6938
6939 /**
6940 * @ngdoc
6941 * @name ng.$q#when
6942 * @methodOf ng.$q
6943 * @description
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.
6947 *
6948 * @param {*} value Value or a promise
6949 * @returns {Promise} Returns a promise of the passed value or promise
6950 */
6951 var when = function(value, callback, errback) {
6952 var result = defer(),
6953 done;
6954
6955 var wrappedCallback = function(value) {
6956 try {
6957 return (callback || defaultCallback)(value);
6958 } catch (e) {
6959 exceptionHandler(e);
6960 return reject(e);
6961 }
6962 };
6963
6964 var wrappedErrback = function(reason) {
6965 try {
6966 return (errback || defaultErrback)(reason);
6967 } catch (e) {
6968 exceptionHandler(e);
6969 return reject(e);
6970 }
6971 };
6972
6973 nextTick(function() {
6974 ref(value).then(function(value) {
6975 if (done) return;
6976 done = true;
6977 result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
6978 }, function(reason) {
6979 if (done) return;
6980 done = true;
6981 result.resolve(wrappedErrback(reason));
6982 });
6983 });
6984
6985 return result.promise;
6986 };
6987
6988
6989 function defaultCallback(value) {
6990 return value;
6991 }
6992
6993
6994 function defaultErrback(reason) {
6995 return reject(reason);
6996 }
6997
6998
6999 /**
7000 * @ngdoc
7001 * @name ng.$q#all
7002 * @methodOf ng.$q
7003 * @description
7004 * Combines multiple promises into a single promise that is resolved when all of the input
7005 * promises are resolved.
7006 *
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
7011 * same rejection.
7012 */
7013 function all(promises) {
7014 var deferred = defer(),
7015 counter = promises.length,
7016 results = [];
7017
7018 if (counter) {
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);
7027 });
7028 });
7029 } else {
7030 deferred.resolve(results);
7031 }
7032
7033 return deferred.promise;
7034 }
7035
7036 return {
7037 defer: defer,
7038 reject: reject,
7039 when: when,
7040 all: all
7041 };
7042 }
7043
7044 /**
7045 * @ngdoc object
7046 * @name ng.$routeProvider
7047 * @function
7048 *
7049 * @description
7050 *
7051 * Used for configuring routes. See {@link ng.$route $route} for an example.
7052 */
7053 function $RouteProvider(){
7054 var routes = {};
7055
7056 /**
7057 * @ngdoc method
7058 * @name ng.$routeProvider#when
7059 * @methodOf ng.$routeProvider
7060 *
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
7064 * route definition.
7065 *
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
7068 * matches.
7069 *
7070 * @param {Object} route Mapping information to be assigned to `$route.current` on route
7071 * match.
7072 *
7073 * Object properties:
7074 *
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:
7088 *
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.
7094 *
7095 * - `redirectTo` – {(string|function())=} – value to update
7096 * {@link ng.$location $location} path with and trigger route redirection.
7097 *
7098 * If `redirectTo` is a function, it will be called with the following parameters:
7099 *
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()`
7104 *
7105 * The custom `redirectTo` function is expected to return a string which will be used
7106 * to update `$location.path()` and `$location.search()`.
7107 *
7108 * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
7109 * changes.
7110 *
7111 * If the option is set to `false` and url in the browser changes, then
7112 * `$routeUpdate` event is broadcasted on the root scope.
7113 *
7114 * @returns {Object} self
7115 *
7116 * @description
7117 * Adds a new route definition to the `$route` service.
7118 */
7119 this.when = function(path, route) {
7120 routes[path] = extend({reloadOnSearch: true}, route);
7121
7122 // create redirection for trailing slashes
7123 if (path) {
7124 var redirectPath = (path[path.length-1] == '/')
7125 ? path.substr(0, path.length-1)
7126 : path +'/';
7127
7128 routes[redirectPath] = {redirectTo: path};
7129 }
7130
7131 return this;
7132 };
7133
7134 /**
7135 * @ngdoc method
7136 * @name ng.$routeProvider#otherwise
7137 * @methodOf ng.$routeProvider
7138 *
7139 * @description
7140 * Sets route definition that will be used on route change when no other route definition
7141 * is matched.
7142 *
7143 * @param {Object} params Mapping information to be assigned to `$route.current`.
7144 * @returns {Object} self
7145 */
7146 this.otherwise = function(params) {
7147 this.when(null, params);
7148 return this;
7149 };
7150
7151
7152 this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
7153 function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) {
7154
7155 /**
7156 * @ngdoc object
7157 * @name ng.$route
7158 * @requires $location
7159 * @requires $routeParams
7160 *
7161 * @property {Object} current Reference to the current route definition.
7162 * The route definition contains:
7163 *
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:
7168 *
7169 * - `$scope` - The current route scope.
7170 * - `$template` - The current route template HTML.
7171 *
7172 * @property {Array.<Object>} routes Array of all configured routes.
7173 *
7174 * @description
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.
7177 *
7178 * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
7179 *
7180 * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
7181 * directive and the {@link ng.$routeParams $routeParams} service.
7182 *
7183 * @example
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.
7186
7187 Note that this example is using {@link ng.directive:script inlined templates}
7188 to get it working on jsfiddle as well.
7189
7190 <example module="ngView">
7191 <file name="index.html">
7192 <div ng-controller="MainCntl">
7193 Choose:
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/>
7199
7200 <div ng-view></div>
7201 <hr />
7202
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>
7208 </div>
7209 </file>
7210
7211 <file name="book.html">
7212 controller: {{name}}<br />
7213 Book Id: {{params.bookId}}<br />
7214 </file>
7215
7216 <file name="chapter.html">
7217 controller: {{name}}<br />
7218 Book Id: {{params.bookId}}<br />
7219 Chapter Id: {{params.chapterId}}
7220 </file>
7221
7222 <file name="script.js">
7223 angular.module('ngView', [], function($routeProvider, $locationProvider) {
7224 $routeProvider.when('/Book/:bookId', {
7225 templateUrl: 'book.html',
7226 controller: BookCntl,
7227 resolve: {
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;
7233 }
7234 }
7235 });
7236 $routeProvider.when('/Book/:bookId/ch/:chapterId', {
7237 templateUrl: 'chapter.html',
7238 controller: ChapterCntl
7239 });
7240
7241 // configure html5 to get links working on jsfiddle
7242 $locationProvider.html5Mode(true);
7243 });
7244
7245 function MainCntl($scope, $route, $routeParams, $location) {
7246 $scope.$route = $route;
7247 $scope.$location = $location;
7248 $scope.$routeParams = $routeParams;
7249 }
7250
7251 function BookCntl($scope, $routeParams) {
7252 $scope.name = "BookCntl";
7253 $scope.params = $routeParams;
7254 }
7255
7256 function ChapterCntl($scope, $routeParams) {
7257 $scope.name = "ChapterCntl";
7258 $scope.params = $routeParams;
7259 }
7260 </file>
7261
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/);
7269
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/);
7275 });
7276 </file>
7277 </example>
7278 */
7279
7280 /**
7281 * @ngdoc event
7282 * @name ng.$route#$routeChangeStart
7283 * @eventOf ng.$route
7284 * @eventType broadcast on root scope
7285 * @description
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.
7291 *
7292 * @param {Route} next Future route information.
7293 * @param {Route} current Current route information.
7294 */
7295
7296 /**
7297 * @ngdoc event
7298 * @name ng.$route#$routeChangeSuccess
7299 * @eventOf ng.$route
7300 * @eventType broadcast on root scope
7301 * @description
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.
7305 *
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.
7309 */
7310
7311 /**
7312 * @ngdoc event
7313 * @name ng.$route#$routeChangeError
7314 * @eventOf ng.$route
7315 * @eventType broadcast on root scope
7316 * @description
7317 * Broadcasted if any of the resolve promises are rejected.
7318 *
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.
7322 */
7323
7324 /**
7325 * @ngdoc event
7326 * @name ng.$route#$routeUpdate
7327 * @eventOf ng.$route
7328 * @eventType broadcast on root scope
7329 * @description
7330 *
7331 * The `reloadOnSearch` property has been set to false, and we are reusing the same
7332 * instance of the Controller.
7333 */
7334
7335 var forceReload = false,
7336 $route = {
7337 routes: routes,
7338
7339 /**
7340 * @ngdoc method
7341 * @name ng.$route#reload
7342 * @methodOf ng.$route
7343 *
7344 * @description
7345 * Causes `$route` service to reload the current route even if
7346 * {@link ng.$location $location} hasn't changed.
7347 *
7348 * As a result of that, {@link ng.directive:ngView ngView}
7349 * creates new scope, reinstantiates the controller.
7350 */
7351 reload: function() {
7352 forceReload = true;
7353 $rootScope.$evalAsync(updateRoute);
7354 }
7355 };
7356
7357 $rootScope.$on('$locationChangeSuccess', updateRoute);
7358
7359 return $route;
7360
7361 /////////////////////////////////////////////////////
7362
7363 /**
7364 * @param on {string} current url
7365 * @param when {string} route when template to match the url against
7366 * @return {?Object}
7367 */
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
7371
7372 // Escape regexp special characters.
7373 when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
7374 var regex = '',
7375 params = [],
7376 dst = {};
7377
7378 var re = /:(\w+)/g,
7379 paramMatch,
7380 lastMatchedIndex = 0;
7381
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;
7389 }
7390 // Append trailing path part.
7391 regex += when.substr(lastMatchedIndex);
7392
7393 var match = on.match(new RegExp(regex));
7394 if (match) {
7395 forEach(params, function(name, index) {
7396 dst[name] = match[index + 1];
7397 });
7398 }
7399 return match ? dst : null;
7400 }
7401
7402 function updateRoute() {
7403 var next = parseRoute(),
7404 last = $route.current;
7405
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;
7415 if (next) {
7416 if (next.redirectTo) {
7417 if (isString(next.redirectTo)) {
7418 $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
7419 .replace();
7420 } else {
7421 $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
7422 .replace();
7423 }
7424 }
7425 }
7426
7427 $q.when(next).
7428 then(function() {
7429 if (next) {
7430 var keys = [],
7431 values = [],
7432 template;
7433
7434 forEach(next.resolve || {}, function(value, key) {
7435 keys.push(key);
7436 values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
7437 });
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; });
7442 }
7443 if (isDefined(template)) {
7444 keys.push('$template');
7445 values.push(template);
7446 }
7447 return $q.all(values).then(function(values) {
7448 var locals = {};
7449 forEach(values, function(value, index) {
7450 locals[keys[index]] = value;
7451 });
7452 return locals;
7453 });
7454 }
7455 }).
7456 // after route change
7457 then(function(locals) {
7458 if (next == $route.current) {
7459 if (next) {
7460 next.locals = locals;
7461 copy(next.params, $routeParams);
7462 }
7463 $rootScope.$broadcast('$routeChangeSuccess', next, last);
7464 }
7465 }, function(error) {
7466 if (next == $route.current) {
7467 $rootScope.$broadcast('$routeChangeError', next, last, error);
7468 }
7469 });
7470 }
7471 }
7472
7473
7474 /**
7475 * @returns the current active route, by matching it against the URL
7476 */
7477 function parseRoute() {
7478 // Match a route
7479 var params, match;
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;
7486 }
7487 });
7488 // No route matched; fallback to "otherwise" route
7489 return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
7490 }
7491
7492 /**
7493 * @returns interpolation of the redirect path with the parametrs
7494 */
7495 function interpolate(string, params) {
7496 var result = [];
7497 forEach((string||'').split(':'), function(segment, i) {
7498 if (i == 0) {
7499 result.push(segment);
7500 } else {
7501 var segmentMatch = segment.match(/(\w+)(.*)/);
7502 var key = segmentMatch[1];
7503 result.push(params[key]);
7504 result.push(segmentMatch[2] || '');
7505 delete params[key];
7506 }
7507 });
7508 return result.join('');
7509 }
7510 }];
7511 }
7512
7513 /**
7514 * @ngdoc object
7515 * @name ng.$routeParams
7516 * @requires $route
7517 *
7518 * @description
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.
7522 *
7523 * In case of parameter name collision, `path` params take precedence over `search` params.
7524 *
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.
7527 *
7528 * @example
7529 * <pre>
7530 * // Given:
7531 * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
7532 * // Route: /Chapter/:chapterId/Section/:sectionId
7533 * //
7534 * // Then
7535 * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
7536 * </pre>
7537 */
7538 function $RouteParamsProvider() {
7539 this.$get = valueFn({});
7540 }
7541
7542 /**
7543 * DESIGN NOTES
7544 *
7545 * The design decisions behind the scope are heavily favored for speed and memory consumption.
7546 *
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.
7549 *
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
7554 *
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)
7558 *
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
7561 *
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.
7565 */
7566
7567
7568 /**
7569 * @ngdoc object
7570 * @name ng.$rootScopeProvider
7571 * @description
7572 *
7573 * Provider for the $rootScope service.
7574 */
7575
7576 /**
7577 * @ngdoc function
7578 * @name ng.$rootScopeProvider#digestTtl
7579 * @methodOf ng.$rootScopeProvider
7580 * @description
7581 *
7582 * Sets the number of digest iterations the scope should attempt to execute before giving up and
7583 * assuming that the model is unstable.
7584 *
7585 * The current default is 10 iterations.
7586 *
7587 * @param {number} limit The number of digest iterations.
7588 */
7589
7590
7591 /**
7592 * @ngdoc object
7593 * @name ng.$rootScope
7594 * @description
7595 *
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}.
7599 */
7600 function $RootScopeProvider(){
7601 var TTL = 10;
7602
7603 this.digestTtl = function(value) {
7604 if (arguments.length) {
7605 TTL = value;
7606 }
7607 return TTL;
7608 };
7609
7610 this.$get = ['$injector', '$exceptionHandler', '$parse',
7611 function( $injector, $exceptionHandler, $parse) {
7612
7613 /**
7614 * @ngdoc function
7615 * @name ng.$rootScope.Scope
7616 *
7617 * @description
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.)
7622 *
7623 * Here is a simple scope snippet to show how you can interact with the scope.
7624 * <pre>
7625 angular.injector(['ng']).invoke(function($rootScope) {
7626 var scope = $rootScope.$new();
7627 scope.salutation = 'Hello';
7628 scope.name = 'World';
7629
7630 expect(scope.greeting).toEqual(undefined);
7631
7632 scope.$watch('name', function() {
7633 scope.greeting = scope.salutation + ' ' + scope.name + '!';
7634 }); // initialize the watch
7635
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);
7640
7641 scope.$digest(); // fire all the watches
7642 expect(scope.greeting).toEqual('Hello Misko!');
7643 });
7644 * </pre>
7645 *
7646 * # Inheritance
7647 * A scope can inherit from a parent scope, as in this example:
7648 * <pre>
7649 var parent = $rootScope;
7650 var child = parent.$new();
7651
7652 parent.salutation = "Hello";
7653 child.name = "World";
7654 expect(child.salutation).toEqual('Hello');
7655
7656 child.salutation = "Welcome";
7657 expect(child.salutation).toEqual('Welcome');
7658 expect(parent.salutation).toEqual('Hello');
7659 * </pre>
7660 *
7661 *
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.
7668 *
7669 */
7670 function 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 = {};
7680 }
7681
7682 /**
7683 * @ngdoc property
7684 * @name ng.$rootScope.Scope#$id
7685 * @propertyOf ng.$rootScope.Scope
7686 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
7687 * debugging.
7688 */
7689
7690
7691 Scope.prototype = {
7692 /**
7693 * @ngdoc function
7694 * @name ng.$rootScope.Scope#$new
7695 * @methodOf ng.$rootScope.Scope
7696 * @function
7697 *
7698 * @description
7699 * Creates a new child {@link ng.$rootScope.Scope scope}.
7700 *
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()}.
7704 *
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.
7708 *
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
7712 * state.
7713 *
7714 * @returns {Object} The newly created child scope.
7715 *
7716 */
7717 $new: function(isolate) {
7718 var Child,
7719 child;
7720
7721 if (isFunction(isolate)) {
7722 // TODO: remove at some point
7723 throw Error('API-CHANGE: Use $controller to instantiate controllers.');
7724 }
7725 if (isolate) {
7726 child = new Scope();
7727 child.$root = this.$root;
7728 } else {
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();
7735 }
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;
7745 } else {
7746 this.$$childHead = this.$$childTail = child;
7747 }
7748 return child;
7749 },
7750
7751 /**
7752 * @ngdoc function
7753 * @name ng.$rootScope.Scope#$watch
7754 * @methodOf ng.$rootScope.Scope
7755 * @function
7756 *
7757 * @description
7758 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
7759 *
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.
7773 *
7774 *
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.)
7779 *
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.
7786 *
7787 *
7788 * # Example
7789 * <pre>
7790 // let's assume that scope was dependency injected as the $rootScope
7791 var scope = $rootScope;
7792 scope.name = 'misko';
7793 scope.counter = 0;
7794
7795 expect(scope.counter).toEqual(0);
7796 scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
7797 expect(scope.counter).toEqual(0);
7798
7799 scope.$digest();
7800 // no variable change
7801 expect(scope.counter).toEqual(0);
7802
7803 scope.name = 'adam';
7804 scope.$digest();
7805 expect(scope.counter).toEqual(1);
7806 * </pre>
7807 *
7808 *
7809 *
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`.
7813 *
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.
7818 *
7819 * - `string`: Evaluated as {@link guide/expression expression}
7820 * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
7821 *
7822 * @param {boolean=} objectEquality Compare object for equality rather than for reference.
7823 * @returns {function()} Returns a deregistration function for this listener.
7824 */
7825 $watch: function(watchExp, listener, objectEquality) {
7826 var scope = this,
7827 get = compileToFn(watchExp, 'watch'),
7828 array = scope.$$watchers,
7829 watcher = {
7830 fn: listener,
7831 last: initWatchVal,
7832 get: get,
7833 exp: watchExp,
7834 eq: !!objectEquality
7835 };
7836
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);};
7841 }
7842
7843 if (!array) {
7844 array = scope.$$watchers = [];
7845 }
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);
7849
7850 return function() {
7851 arrayRemove(array, watcher);
7852 };
7853 },
7854
7855 /**
7856 * @ngdoc function
7857 * @name ng.$rootScope.Scope#$digest
7858 * @methodOf ng.$rootScope.Scope
7859 * @function
7860 *
7861 * @description
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.
7867 *
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()`.
7873 *
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`.
7877 *
7878 * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
7879 * life-cycle.
7880 *
7881 * # Example
7882 * <pre>
7883 var scope = ...;
7884 scope.name = 'misko';
7885 scope.counter = 0;
7886
7887 expect(scope.counter).toEqual(0);
7888 scope.$watch('name', function(newValue, oldValue) {
7889 scope.counter = scope.counter + 1;
7890 });
7891 expect(scope.counter).toEqual(0);
7892
7893 scope.$digest();
7894 // no variable change
7895 expect(scope.counter).toEqual(0);
7896
7897 scope.name = 'adam';
7898 scope.$digest();
7899 expect(scope.counter).toEqual(1);
7900 * </pre>
7901 *
7902 */
7903 $digest: function() {
7904 var watch, value, last,
7905 watchers,
7906 asyncQueue,
7907 length,
7908 dirty, ttl = TTL,
7909 next, current, target = this,
7910 watchLog = [],
7911 logIdx, logMsg;
7912
7913 beginPhase('$digest');
7914
7915 do {
7916 dirty = false;
7917 current = target;
7918 do {
7919 asyncQueue = current.$$asyncQueue;
7920 while(asyncQueue.length) {
7921 try {
7922 current.$eval(asyncQueue.shift());
7923 } catch (e) {
7924 $exceptionHandler(e);
7925 }
7926 }
7927 if ((watchers = current.$$watchers)) {
7928 // process our watches
7929 length = watchers.length;
7930 while (length--) {
7931 try {
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) &&
7936 !(watch.eq
7937 ? equals(value, last)
7938 : (typeof value == 'number' && typeof last == 'number'
7939 && isNaN(value) && isNaN(last)))) {
7940 dirty = true;
7941 watch.last = watch.eq ? copy(value) : value;
7942 watch.fn(value, ((last === initWatchVal) ? value : last), current);
7943 if (ttl < 5) {
7944 logIdx = 4 - ttl;
7945 if (!watchLog[logIdx]) watchLog[logIdx] = [];
7946 logMsg = (isFunction(watch.exp))
7947 ? 'fn: ' + (watch.exp.name || watch.exp.toString())
7948 : watch.exp;
7949 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
7950 watchLog[logIdx].push(logMsg);
7951 }
7952 }
7953 } catch (e) {
7954 $exceptionHandler(e);
7955 }
7956 }
7957 }
7958
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;
7965 }
7966 }
7967 } while ((current = next));
7968
7969 if(dirty && !(ttl--)) {
7970 clearPhase();
7971 throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
7972 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
7973 }
7974 } while (dirty || asyncQueue.length);
7975
7976 clearPhase();
7977 },
7978
7979
7980 /**
7981 * @ngdoc event
7982 * @name ng.$rootScope.Scope#$destroy
7983 * @eventOf ng.$rootScope.Scope
7984 * @eventType broadcast on scope being destroyed
7985 *
7986 * @description
7987 * Broadcasted when a scope and its children are being destroyed.
7988 */
7989
7990 /**
7991 * @ngdoc function
7992 * @name ng.$rootScope.Scope#$destroy
7993 * @methodOf ng.$rootScope.Scope
7994 * @function
7995 *
7996 * @description
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.
8001 *
8002 * The `$destroy()` is usually used by directives such as
8003 * {@link ng.directive:ngRepeat ngRepeat} for managing the
8004 * unrolling of the loop.
8005 *
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.
8009 */
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;
8014
8015 this.$broadcast('$destroy');
8016 this.$$destroyed = true;
8017
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;
8022
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;
8027 },
8028
8029 /**
8030 * @ngdoc function
8031 * @name ng.$rootScope.Scope#$eval
8032 * @methodOf ng.$rootScope.Scope
8033 * @function
8034 *
8035 * @description
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.
8038 *
8039 * # Example
8040 * <pre>
8041 var scope = ng.$rootScope.Scope();
8042 scope.a = 1;
8043 scope.b = 2;
8044
8045 expect(scope.$eval('a+b')).toEqual(3);
8046 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
8047 * </pre>
8048 *
8049 * @param {(string|function())=} expression An angular expression to be executed.
8050 *
8051 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
8052 * - `function(scope)`: execute the function with the current `scope` parameter.
8053 *
8054 * @returns {*} The result of evaluating the expression.
8055 */
8056 $eval: function(expr, locals) {
8057 return $parse(expr)(this, locals);
8058 },
8059
8060 /**
8061 * @ngdoc function
8062 * @name ng.$rootScope.Scope#$evalAsync
8063 * @methodOf ng.$rootScope.Scope
8064 * @function
8065 *
8066 * @description
8067 * Executes the expression on the current scope at a later point in time.
8068 *
8069 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
8070 *
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.
8074 *
8075 * Any exceptions from the execution of the expression are forwarded to the
8076 * {@link ng.$exceptionHandler $exceptionHandler} service.
8077 *
8078 * @param {(string|function())=} expression An angular expression to be executed.
8079 *
8080 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
8081 * - `function(scope)`: execute the function with the current `scope` parameter.
8082 *
8083 */
8084 $evalAsync: function(expr) {
8085 this.$$asyncQueue.push(expr);
8086 },
8087
8088 /**
8089 * @ngdoc function
8090 * @name ng.$rootScope.Scope#$apply
8091 * @methodOf ng.$rootScope.Scope
8092 * @function
8093 *
8094 * @description
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}.
8100 *
8101 * ## Life cycle
8102 *
8103 * # Pseudo-Code of `$apply()`
8104 * <pre>
8105 function $apply(expr) {
8106 try {
8107 return $eval(expr);
8108 } catch (e) {
8109 $exceptionHandler(e);
8110 } finally {
8111 $root.$digest();
8112 }
8113 }
8114 * </pre>
8115 *
8116 *
8117 * Scope's `$apply()` method transitions through the following stages:
8118 *
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.
8125 *
8126 *
8127 * @param {(string|function())=} exp An angular expression to be executed.
8128 *
8129 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
8130 * - `function(scope)`: execute the function with current `scope` parameter.
8131 *
8132 * @returns {*} The result of evaluating the expression.
8133 */
8134 $apply: function(expr) {
8135 try {
8136 beginPhase('$apply');
8137 return this.$eval(expr);
8138 } catch (e) {
8139 $exceptionHandler(e);
8140 } finally {
8141 clearPhase();
8142 try {
8143 $rootScope.$digest();
8144 } catch (e) {
8145 $exceptionHandler(e);
8146 throw e;
8147 }
8148 }
8149 },
8150
8151 /**
8152 * @ngdoc function
8153 * @name ng.$rootScope.Scope#$on
8154 * @methodOf ng.$rootScope.Scope
8155 * @function
8156 *
8157 * @description
8158 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
8159 * event life cycle.
8160 *
8161 * The event listener function format is: `function(event, args...)`. The `event` object
8162 * passed into the listener has the following attributes:
8163 *
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.
8171 *
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.
8175 */
8176 $on: function(name, listener) {
8177 var namedListeners = this.$$listeners[name];
8178 if (!namedListeners) {
8179 this.$$listeners[name] = namedListeners = [];
8180 }
8181 namedListeners.push(listener);
8182
8183 return function() {
8184 namedListeners[indexOf(namedListeners, listener)] = null;
8185 };
8186 },
8187
8188
8189 /**
8190 * @ngdoc function
8191 * @name ng.$rootScope.Scope#$emit
8192 * @methodOf ng.$rootScope.Scope
8193 * @function
8194 *
8195 * @description
8196 * Dispatches an event `name` upwards through the scope hierarchy notifying the
8197 * registered {@link ng.$rootScope.Scope#$on} listeners.
8198 *
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.
8203 *
8204 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
8205 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
8206 *
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}
8210 */
8211 $emit: function(name, args) {
8212 var empty = [],
8213 namedListeners,
8214 scope = this,
8215 stopPropagation = false,
8216 event = {
8217 name: name,
8218 targetScope: scope,
8219 stopPropagation: function() {stopPropagation = true;},
8220 preventDefault: function() {
8221 event.defaultPrevented = true;
8222 },
8223 defaultPrevented: false
8224 },
8225 listenerArgs = concat([event], arguments, 1),
8226 i, length;
8227
8228 do {
8229 namedListeners = scope.$$listeners[name] || empty;
8230 event.currentScope = scope;
8231 for (i=0, length=namedListeners.length; i<length; i++) {
8232
8233 // if listeners were deregistered, defragment the array
8234 if (!namedListeners[i]) {
8235 namedListeners.splice(i, 1);
8236 i--;
8237 length--;
8238 continue;
8239 }
8240 try {
8241 namedListeners[i].apply(null, listenerArgs);
8242 if (stopPropagation) return event;
8243 } catch (e) {
8244 $exceptionHandler(e);
8245 }
8246 }
8247 //traverse upwards
8248 scope = scope.$parent;
8249 } while (scope);
8250
8251 return event;
8252 },
8253
8254
8255 /**
8256 * @ngdoc function
8257 * @name ng.$rootScope.Scope#$broadcast
8258 * @methodOf ng.$rootScope.Scope
8259 * @function
8260 *
8261 * @description
8262 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
8263 * registered {@link ng.$rootScope.Scope#$on} listeners.
8264 *
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.
8269 *
8270 * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
8271 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
8272 *
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}
8276 */
8277 $broadcast: function(name, args) {
8278 var target = this,
8279 current = target,
8280 next = target,
8281 event = {
8282 name: name,
8283 targetScope: target,
8284 preventDefault: function() {
8285 event.defaultPrevented = true;
8286 },
8287 defaultPrevented: false
8288 },
8289 listenerArgs = concat([event], arguments, 1),
8290 listeners, i, length;
8291
8292 //down while you can, then up and next sibling or up and next sibling until back at root
8293 do {
8294 current = next;
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);
8301 i--;
8302 length--;
8303 continue;
8304 }
8305
8306 try {
8307 listeners[i].apply(null, listenerArgs);
8308 } catch(e) {
8309 $exceptionHandler(e);
8310 }
8311 }
8312
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;
8319 }
8320 }
8321 } while ((current = next));
8322
8323 return event;
8324 }
8325 };
8326
8327 var $rootScope = new Scope();
8328
8329 return $rootScope;
8330
8331
8332 function beginPhase(phase) {
8333 if ($rootScope.$$phase) {
8334 throw Error($rootScope.$$phase + ' already in progress');
8335 }
8336
8337 $rootScope.$$phase = phase;
8338 }
8339
8340 function clearPhase() {
8341 $rootScope.$$phase = null;
8342 }
8343
8344 function compileToFn(exp, name) {
8345 var fn = $parse(exp);
8346 assertArgFn(fn, name);
8347 return fn;
8348 }
8349
8350 /**
8351 * function used as an initial value for watchers.
8352 * because it's unique we can easily tell it apart from other values
8353 */
8354 function initWatchVal() {}
8355 }];
8356 }
8357
8358 /**
8359 * !!! This is an undocumented "private" service !!!
8360 *
8361 * @name ng.$sniffer
8362 * @requires $window
8363 *
8364 * @property {boolean} history Does the browser support html5 history api ?
8365 * @property {boolean} hashchange Does the browser support hashchange event ?
8366 *
8367 * @description
8368 * This is very simple implementation of testing browser's features.
8369 */
8370 function $SnifferProvider() {
8371 this.$get = ['$window', function($window) {
8372 var eventSupport = {},
8373 android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]);
8374
8375 return {
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;
8389
8390 if (isUndefined(eventSupport[event])) {
8391 var divElm = $window.document.createElement('div');
8392 eventSupport[event] = 'on' + event in divElm;
8393 }
8394
8395 return eventSupport[event];
8396 },
8397 // TODO(i): currently there is no way to feature detect CSP without triggering alerts
8398 csp: false
8399 };
8400 }];
8401 }
8402
8403 /**
8404 * @ngdoc object
8405 * @name ng.$window
8406 *
8407 * @description
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.
8412 *
8413 * All expressions are evaluated with respect to current scope so they don't
8414 * suffer from window globality.
8415 *
8416 * @example
8417 <doc:example>
8418 <doc:source>
8419 <script>
8420 function Ctrl($scope, $window) {
8421 $scope.$window = $window;
8422 $scope.greeting = 'Hello, World!';
8423 }
8424 </script>
8425 <div ng-controller="Ctrl">
8426 <input type="text" ng-model="greeting" />
8427 <button ng-click="$window.alert(greeting)">ALERT</button>
8428 </div>
8429 </doc:source>
8430 <doc:scenario>
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();
8435 });
8436 </doc:scenario>
8437 </doc:example>
8438 */
8439 function $WindowProvider(){
8440 this.$get = valueFn(window);
8441 }
8442
8443 /**
8444 * Parse headers into key value object
8445 *
8446 * @param {string} headers Raw headers as a string
8447 * @returns {Object} Parsed headers as key value object
8448 */
8449 function parseHeaders(headers) {
8450 var parsed = {}, key, val, i;
8451
8452 if (!headers) return parsed;
8453
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));
8458
8459 if (key) {
8460 if (parsed[key]) {
8461 parsed[key] += ', ' + val;
8462 } else {
8463 parsed[key] = val;
8464 }
8465 }
8466 });
8467
8468 return parsed;
8469 }
8470
8471
8472 /**
8473 * Returns a function that provides access to parsed headers.
8474 *
8475 * Headers are lazy parsed when first requested.
8476 * @see parseHeaders
8477 *
8478 * @param {(string|Object)} headers Headers to provide access to.
8479 * @returns {function(string=)} Returns a getter function which if called with:
8480 *
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.
8483 */
8484 function headersGetter(headers) {
8485 var headersObj = isObject(headers) ? headers : undefined;
8486
8487 return function(name) {
8488 if (!headersObj) headersObj = parseHeaders(headers);
8489
8490 if (name) {
8491 return headersObj[lowercase(name)] || null;
8492 }
8493
8494 return headersObj;
8495 };
8496 }
8497
8498
8499 /**
8500 * Chain all given functions
8501 *
8502 * This function is used for both request and response transforming
8503 *
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.
8508 */
8509 function transformData(data, headers, fns) {
8510 if (isFunction(fns))
8511 return fns(data, headers);
8512
8513 forEach(fns, function(fn) {
8514 data = fn(data, headers);
8515 });
8516
8517 return data;
8518 }
8519
8520
8521 function isSuccess(status) {
8522 return 200 <= status && status < 300;
8523 }
8524
8525
8526 function $HttpProvider() {
8527 var JSON_START = /^\s*(\[|\{[^\{])/,
8528 JSON_END = /[\}\]]\s*$/,
8529 PROTECTION_PREFIX = /^\)\]\}',?\n/;
8530
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);
8539 }
8540 return data;
8541 }],
8542
8543 // transform outgoing request data
8544 transformRequest: [function(d) {
8545 return isObject(d) && !isFile(d) ? toJson(d) : d;
8546 }],
8547
8548 // default headers
8549 headers: {
8550 common: {
8551 'Accept': 'application/json, text/plain, */*',
8552 'X-Requested-With': 'XMLHttpRequest'
8553 },
8554 post: {'Content-Type': 'application/json;charset=utf-8'},
8555 put: {'Content-Type': 'application/json;charset=utf-8'}
8556 }
8557 };
8558
8559 var providerResponseInterceptors = this.responseInterceptors = [];
8560
8561 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
8562 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
8563
8564 var defaultCache = $cacheFactory('$http'),
8565 responseInterceptors = [];
8566
8567 forEach(providerResponseInterceptors, function(interceptor) {
8568 responseInterceptors.push(
8569 isString(interceptor)
8570 ? $injector.get(interceptor)
8571 : $injector.invoke(interceptor)
8572 );
8573 });
8574
8575
8576 /**
8577 * @ngdoc function
8578 * @name ng.$http
8579 * @requires $httpBackend
8580 * @requires $browser
8581 * @requires $cacheFactory
8582 * @requires $rootScope
8583 * @requires $q
8584 * @requires $injector
8585 *
8586 * @description
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}.
8590 *
8591 * For unit testing applications that use `$http` service, see
8592 * {@link ngMock.$httpBackend $httpBackend mock}.
8593 *
8594 * For a higher level of abstraction, please check out the {@link ngResource.$resource
8595 * $resource} service.
8596 *
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.
8600 *
8601 *
8602 * # General usage
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`.
8606 *
8607 * <pre>
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
8612 * }).
8613 * error(function(data, status, headers, config) {
8614 * // called asynchronously if an error occurs
8615 * // or server returns response with an error status.
8616 * });
8617 * </pre>
8618 *
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
8622 * details.
8623 *
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.
8628 *
8629 * # Shortcut methods
8630 *
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
8633 * were created:
8634 *
8635 * <pre>
8636 * $http.get('/someUrl').success(successCallback);
8637 * $http.post('/someUrl', data).success(successCallback);
8638 * </pre>
8639 *
8640 * Complete list of shortcut methods:
8641 *
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}
8648 *
8649 *
8650 * # Setting HTTP Headers
8651 *
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:
8655 *
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`
8663 *
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'`.
8668 *
8669 * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same
8670 * fashion.
8671 *
8672 *
8673 * # Transforming Requests and Responses
8674 *
8675 * Both requests and responses can be transformed using transform functions. By default, Angular
8676 * applies these transformations:
8677 *
8678 * Request transformations:
8679 *
8680 * - If the `data` property of the request configuration object contains an object, serialize it into
8681 * JSON format.
8682 *
8683 * Response transformations:
8684 *
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.
8687 *
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.
8693 *
8694 * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
8695 * `transformResponse` properties of the configuration object passed into `$http`.
8696 *
8697 *
8698 * # Caching
8699 *
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.
8703 *
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.
8706 *
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.
8710 *
8711 *
8712 * # Response interceptors
8713 *
8714 * Before you start creating interceptors, be sure to understand the
8715 * {@link ng.$q $q and deferred/promise APIs}.
8716 *
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.
8722 *
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.
8727 *
8728 * <pre>
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
8738 * }
8739 * return $q.reject(response);
8740 * });
8741 * }
8742 * });
8743 *
8744 * $httpProvider.responseInterceptors.push('myHttpInterceptor');
8745 *
8746 *
8747 * // register the interceptor via an anonymous factory
8748 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
8749 * return function(promise) {
8750 * // same as above
8751 * }
8752 * });
8753 * </pre>
8754 *
8755 *
8756 * # Security Considerations
8757 *
8758 * When designing web applications, consider security threats from:
8759 *
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}
8763 *
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.
8767 *
8768 * ## JSON Vulnerability Protection
8769 *
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.
8775 *
8776 * For example if your server needs to return:
8777 * <pre>
8778 * ['one','two']
8779 * </pre>
8780 *
8781 * which is vulnerable to attack, your server can return:
8782 * <pre>
8783 * )]}',
8784 * ['one','two']
8785 * </pre>
8786 *
8787 * Angular will strip the prefix, before processing the JSON.
8788 *
8789 *
8790 * ## Cross Site Request Forgery (XSRF) Protection
8791 *
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.
8798 *
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.
8806 *
8807 *
8808 * @param {object} config Object describing the request to be made and how it should be
8809 * processed. The object has following properties:
8810 *
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
8826 * caching.
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.
8831 *
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:
8839 *
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.
8844 *
8845 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
8846 * requests. This is primarily meant to be used for debugging purposes.
8847 *
8848 *
8849 * @example
8850 <example>
8851 <file name="index.html">
8852 <div ng-controller="FetchCtrl">
8853 <select ng-model="method">
8854 <option>GET</option>
8855 <option>JSONP</option>
8856 </select>
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>
8864 </div>
8865 </file>
8866 <file name="script.js">
8867 function FetchCtrl($scope, $http, $templateCache) {
8868 $scope.method = 'GET';
8869 $scope.url = 'http-hello.html';
8870
8871 $scope.fetch = function() {
8872 $scope.code = null;
8873 $scope.response = null;
8874
8875 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
8876 success(function(data, status) {
8877 $scope.status = status;
8878 $scope.data = data;
8879 }).
8880 error(function(data, status) {
8881 $scope.data = data || "Request failed";
8882 $scope.status = status;
8883 });
8884 };
8885
8886 $scope.updateModel = function(method, url) {
8887 $scope.method = method;
8888 $scope.url = url;
8889 };
8890 }
8891 </file>
8892 <file name="http-hello.html">
8893 Hello, $http!
8894 </file>
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!/);
8901 });
8902
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!/);
8908 });
8909
8910 it('should make JSONP request to invalid URL and invoke the error handler',
8911 function() {
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');
8916 });
8917 </file>
8918 </example>
8919 */
8920 function $http(config) {
8921 config.method = uppercase(config.method);
8922
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),
8929 promise;
8930
8931 // strip content-type if data is undefined
8932 if (isUndefined(config.data)) {
8933 delete reqHeaders['Content-Type'];
8934 }
8935
8936 // send request
8937 promise = sendReq(config, reqData, reqHeaders);
8938
8939
8940 // transform future response
8941 promise = promise.then(transformResponse, transformResponse);
8942
8943 // apply interceptors
8944 forEach(responseInterceptors, function(interceptor) {
8945 promise = interceptor(promise);
8946 });
8947
8948 promise.success = function(fn) {
8949 promise.then(function(response) {
8950 fn(response.data, response.status, response.headers, config);
8951 });
8952 return promise;
8953 };
8954
8955 promise.error = function(fn) {
8956 promise.then(null, function(response) {
8957 fn(response.data, response.status, response.headers, config);
8958 });
8959 return promise;
8960 };
8961
8962 return promise;
8963
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)
8968 });
8969 return (isSuccess(response.status))
8970 ? resp
8971 : $q.reject(resp);
8972 }
8973 }
8974
8975 $http.pendingRequests = [];
8976
8977 /**
8978 * @ngdoc method
8979 * @name ng.$http#get
8980 * @methodOf ng.$http
8981 *
8982 * @description
8983 * Shortcut method to perform `GET` request.
8984 *
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
8988 */
8989
8990 /**
8991 * @ngdoc method
8992 * @name ng.$http#delete
8993 * @methodOf ng.$http
8994 *
8995 * @description
8996 * Shortcut method to perform `DELETE` request.
8997 *
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
9001 */
9002
9003 /**
9004 * @ngdoc method
9005 * @name ng.$http#head
9006 * @methodOf ng.$http
9007 *
9008 * @description
9009 * Shortcut method to perform `HEAD` request.
9010 *
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
9014 */
9015
9016 /**
9017 * @ngdoc method
9018 * @name ng.$http#jsonp
9019 * @methodOf ng.$http
9020 *
9021 * @description
9022 * Shortcut method to perform `JSONP` request.
9023 *
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
9028 */
9029 createShortMethods('get', 'delete', 'head', 'jsonp');
9030
9031 /**
9032 * @ngdoc method
9033 * @name ng.$http#post
9034 * @methodOf ng.$http
9035 *
9036 * @description
9037 * Shortcut method to perform `POST` request.
9038 *
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
9043 */
9044
9045 /**
9046 * @ngdoc method
9047 * @name ng.$http#put
9048 * @methodOf ng.$http
9049 *
9050 * @description
9051 * Shortcut method to perform `PUT` request.
9052 *
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
9057 */
9058 createShortMethodsWithData('post', 'put');
9059
9060 /**
9061 * @ngdoc property
9062 * @name ng.$http#defaults
9063 * @propertyOf ng.$http
9064 *
9065 * @description
9066 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
9067 * default headers as well as request and response transformations.
9068 *
9069 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
9070 */
9071 $http.defaults = $config;
9072
9073
9074 return $http;
9075
9076
9077 function createShortMethods(names) {
9078 forEach(arguments, function(name) {
9079 $http[name] = function(url, config) {
9080 return $http(extend(config || {}, {
9081 method: name,
9082 url: url
9083 }));
9084 };
9085 });
9086 }
9087
9088
9089 function createShortMethodsWithData(name) {
9090 forEach(arguments, function(name) {
9091 $http[name] = function(url, data, config) {
9092 return $http(extend(config || {}, {
9093 method: name,
9094 url: url,
9095 data: data
9096 }));
9097 };
9098 });
9099 }
9100
9101
9102 /**
9103 * Makes the request.
9104 *
9105 * !!! ACCESSES CLOSURE VARS:
9106 * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
9107 */
9108 function sendReq(config, reqData, reqHeaders) {
9109 var deferred = $q.defer(),
9110 promise = deferred.promise,
9111 cache,
9112 cachedResp,
9113 url = buildUrl(config.url, config.params);
9114
9115 $http.pendingRequests.push(config);
9116 promise.then(removePendingReq, removePendingReq);
9117
9118
9119 if (config.cache && config.method == 'GET') {
9120 cache = isObject(config.cache) ? config.cache : defaultCache;
9121 }
9122
9123 if (cache) {
9124 cachedResp = cache.get(url);
9125 if (cachedResp) {
9126 if (cachedResp.then) {
9127 // cached request has already been sent, but there is no response yet
9128 cachedResp.then(removePendingReq, removePendingReq);
9129 return cachedResp;
9130 } else {
9131 // serving from cache
9132 if (isArray(cachedResp)) {
9133 resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
9134 } else {
9135 resolvePromise(cachedResp, 200, {});
9136 }
9137 }
9138 } else {
9139 // put the promise for the non-transformed response into cache as a placeholder
9140 cache.put(url, promise);
9141 }
9142 }
9143
9144 // if we won't have the response in cache, send the request to the backend
9145 if (!cachedResp) {
9146 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
9147 config.withCredentials);
9148 }
9149
9150 return promise;
9151
9152
9153 /**
9154 * Callback registered to $httpBackend():
9155 * - caches the response if desired
9156 * - resolves the raw $http promise
9157 * - calls $apply
9158 */
9159 function done(status, response, headersString) {
9160 if (cache) {
9161 if (isSuccess(status)) {
9162 cache.put(url, [status, response, parseHeaders(headersString)]);
9163 } else {
9164 // remove promise from the cache
9165 cache.remove(url);
9166 }
9167 }
9168
9169 resolvePromise(response, status, headersString);
9170 $rootScope.$apply();
9171 }
9172
9173
9174 /**
9175 * Resolves the raw $http promise.
9176 */
9177 function resolvePromise(response, status, headers) {
9178 // normalize internal statuses to 0
9179 status = Math.max(status, 0);
9180
9181 (isSuccess(status) ? deferred.resolve : deferred.reject)({
9182 data: response,
9183 status: status,
9184 headers: headersGetter(headers),
9185 config: config
9186 });
9187 }
9188
9189
9190 function removePendingReq() {
9191 var idx = indexOf($http.pendingRequests, config);
9192 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
9193 }
9194 }
9195
9196
9197 function buildUrl(url, params) {
9198 if (!params) return url;
9199 var parts = [];
9200 forEachSorted(params, function(value, key) {
9201 if (value == null || value == undefined) return;
9202 if (isObject(value)) {
9203 value = toJson(value);
9204 }
9205 parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
9206 });
9207 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
9208 }
9209
9210
9211 }];
9212 }
9213
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.");
9219 };
9220
9221
9222 /**
9223 * @ngdoc object
9224 * @name ng.$httpBackend
9225 * @requires $browser
9226 * @requires $window
9227 * @requires $document
9228 *
9229 * @description
9230 * HTTP backend used by the {@link ng.$http service} that delegates to
9231 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
9232 *
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}.
9235 *
9236 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
9237 * $httpBackend} which can be trained with responses.
9238 */
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(':', ''));
9243 }];
9244 }
9245
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();
9251
9252 if (lowercase(method) == 'jsonp') {
9253 var callbackId = '_' + (callbacks.counter++).toString(36);
9254 callbacks[callbackId] = function(data) {
9255 callbacks[callbackId].data = data;
9256 };
9257
9258 jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
9259 function() {
9260 if (callbacks[callbackId].data) {
9261 completeRequest(callback, 200, callbacks[callbackId].data);
9262 } else {
9263 completeRequest(callback, -2);
9264 }
9265 delete callbacks[callbackId];
9266 });
9267 } else {
9268 var xhr = new XHR();
9269 xhr.open(method, url, true);
9270 forEach(headers, function(value, key) {
9271 if (value) xhr.setRequestHeader(key, value);
9272 });
9273
9274 var status;
9275
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
9278 // always async
9279 xhr.onreadystatechange = function() {
9280 if (xhr.readyState == 4) {
9281 var responseHeaders = xhr.getAllResponseHeaders();
9282
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.
9287
9288 // CORS "simple response headers" http://www.w3.org/TR/cors/
9289 var value,
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);
9296 if (value) {
9297 responseHeaders += header + ": " + value + "\n";
9298 }
9299 });
9300 }
9301 // end of the workaround.
9302
9303 completeRequest(callback, status || xhr.status, xhr.responseText,
9304 responseHeaders);
9305 }
9306 };
9307
9308 if (withCredentials) {
9309 xhr.withCredentials = true;
9310 }
9311
9312 xhr.send(post || '');
9313
9314 if (timeout > 0) {
9315 $browserDefer(function() {
9316 status = -1;
9317 xhr.abort();
9318 }, timeout);
9319 }
9320 }
9321
9322
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];
9326
9327 // fix status code for file protocol (it's always 0)
9328 status = (protocol == 'file') ? (response ? 200 : 404) : status;
9329
9330 // normalize IE bug (http://bugs.jquery.com/ticket/1450)
9331 status = status == 1223 ? 204 : status;
9332
9333 callback(status, response, headersString);
9334 $browser.$$completeOutstandingRequest(noop);
9335 }
9336 };
9337
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);
9345 if (done) done();
9346 };
9347
9348 script.type = 'text/javascript';
9349 script.src = url;
9350
9351 if (msie) {
9352 script.onreadystatechange = function() {
9353 if (/loaded|complete/.test(script.readyState)) doneWrapper();
9354 };
9355 } else {
9356 script.onload = script.onerror = doneWrapper;
9357 }
9358
9359 rawDocument.body.appendChild(script);
9360 }
9361 }
9362
9363 /**
9364 * @ngdoc object
9365 * @name ng.$locale
9366 *
9367 * @description
9368 * $locale service provides localization rules for various Angular components. As of right now the
9369 * only public api is:
9370 *
9371 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
9372 */
9373 function $LocaleProvider(){
9374 this.$get = function() {
9375 return {
9376 id: 'en-us',
9377
9378 NUMBER_FORMATS: {
9379 DECIMAL_SEP: '.',
9380 GROUP_SEP: ',',
9381 PATTERNS: [
9382 { // Decimal Pattern
9383 minInt: 1,
9384 minFrac: 0,
9385 maxFrac: 3,
9386 posPre: '',
9387 posSuf: '',
9388 negPre: '-',
9389 negSuf: '',
9390 gSize: 3,
9391 lgSize: 3
9392 },{ //Currency Pattern
9393 minInt: 1,
9394 minFrac: 2,
9395 maxFrac: 2,
9396 posPre: '\u00A4',
9397 posSuf: '',
9398 negPre: '(\u00A4',
9399 negSuf: ')',
9400 gSize: 3,
9401 lgSize: 3
9402 }
9403 ],
9404 CURRENCY_SYM: '$'
9405 },
9406
9407 DATETIME_FORMATS: {
9408 MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
9409 .split(','),
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(','),
9413 AMPMS: ['AM','PM'],
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',
9421 shortTime: 'h:mm a'
9422 },
9423
9424 pluralCat: function(num) {
9425 if (num === 1) {
9426 return 'one';
9427 }
9428 return 'other';
9429 }
9430 };
9431 };
9432 }
9433
9434 function $TimeoutProvider() {
9435 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
9436 function($rootScope, $browser, $q, $exceptionHandler) {
9437 var deferreds = {};
9438
9439
9440 /**
9441 * @ngdoc function
9442 * @name ng.$timeout
9443 * @requires $browser
9444 *
9445 * @description
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.
9449 *
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.
9452 *
9453 * To cancel a timeout request, call `$timeout.cancel(promise)`.
9454 *
9455 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
9456 * synchronously flush the queue of deferred functions.
9457 *
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.
9464 */
9465 function timeout(fn, delay, invokeApply) {
9466 var deferred = $q.defer(),
9467 promise = deferred.promise,
9468 skipApply = (isDefined(invokeApply) && !invokeApply),
9469 timeoutId, cleanup;
9470
9471 timeoutId = $browser.defer(function() {
9472 try {
9473 deferred.resolve(fn());
9474 } catch(e) {
9475 deferred.reject(e);
9476 $exceptionHandler(e);
9477 }
9478
9479 if (!skipApply) $rootScope.$apply();
9480 }, delay);
9481
9482 cleanup = function() {
9483 delete deferreds[promise.$$timeoutId];
9484 };
9485
9486 promise.$$timeoutId = timeoutId;
9487 deferreds[timeoutId] = deferred;
9488 promise.then(cleanup, cleanup);
9489
9490 return promise;
9491 }
9492
9493
9494 /**
9495 * @ngdoc function
9496 * @name ng.$timeout#cancel
9497 * @methodOf ng.$timeout
9498 *
9499 * @description
9500 * Cancels a task associated with the `promise`. As a result of this, the promise will be
9501 * resolved with a rejection.
9502 *
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
9505 * canceled.
9506 */
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);
9511 }
9512 return false;
9513 };
9514
9515 return timeout;
9516 }];
9517 }
9518
9519 /**
9520 * @ngdoc object
9521 * @name ng.$filterProvider
9522 * @description
9523 *
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.
9527 *
9528 * <pre>
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 + '!';
9534 * });
9535 *
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;
9544 * };
9545 * });
9546 * }
9547 * </pre>
9548 *
9549 * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
9550 * <pre>
9551 * it('should be the same instance', inject(
9552 * function($filterProvider) {
9553 * $filterProvider.register('reverse', function(){
9554 * return ...;
9555 * });
9556 * },
9557 * function($filter, reverseFilter) {
9558 * expect($filter('reverse')).toBe(reverseFilter);
9559 * });
9560 * </pre>
9561 *
9562 *
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
9565 * Guide.
9566 */
9567 /**
9568 * @ngdoc method
9569 * @name ng.$filterProvider#register
9570 * @methodOf ng.$filterProvider
9571 * @description
9572 * Register filter factory function.
9573 *
9574 * @param {String} name Name of the filter.
9575 * @param {function} fn The filter factory function which is injectable.
9576 */
9577
9578
9579 /**
9580 * @ngdoc function
9581 * @name ng.$filter
9582 * @function
9583 * @description
9584 * Filters are used for formatting data displayed to the user.
9585 *
9586 * The general syntax in templates is as follows:
9587 *
9588 * {{ expression [| filter_name[:parameter_value] ... ] }}
9589 *
9590 * @param {String} name Name of the filter function to retrieve
9591 * @return {Function} the filter function
9592 */
9593 $FilterProvider.$inject = ['$provide'];
9594 function $FilterProvider($provide) {
9595 var suffix = 'Filter';
9596
9597 function register(name, factory) {
9598 return $provide.factory(name + suffix, factory);
9599 }
9600 this.register = register;
9601
9602 this.$get = ['$injector', function($injector) {
9603 return function(name) {
9604 return $injector.get(name + suffix);
9605 }
9606 }];
9607
9608 ////////////////////////////////////////
9609
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);
9619 }
9620
9621 /**
9622 * @ngdoc filter
9623 * @name ng.filter:filter
9624 * @function
9625 *
9626 * @description
9627 * Selects a subset of items from `array` and returns it as a new array.
9628 *
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.
9631 *
9632 * @param {Array} array The source array.
9633 * @param {string|Object|function()} expression The predicate to be used for selecting items from
9634 * `array`.
9635 *
9636 * Can be one of:
9637 *
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 `!`.
9641 *
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.
9648 *
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.
9652 *
9653 * @example
9654 <doc:example>
9655 <doc:source>
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>
9661
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>
9668 </tr>
9669 </table>
9670 <hr>
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>
9679 </tr>
9680 </table>
9681 </doc:source>
9682 <doc:scenario>
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']);
9687
9688 input('searchText').enter('76');
9689 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
9690 toEqual(['John', 'Julie']);
9691 });
9692
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']);
9697 });
9698 </doc:scenario>
9699 </doc:example>
9700 */
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)) {
9708 return false;
9709 }
9710 }
9711 return true;
9712 };
9713 var search = function(obj, text){
9714 if (text.charAt(0) === '!') {
9715 return !search(obj, text.substr(1));
9716 }
9717 switch (typeof obj) {
9718 case "boolean":
9719 case "number":
9720 case "string":
9721 return ('' + obj).toLowerCase().indexOf(text) > -1;
9722 case "object":
9723 for ( var objKey in obj) {
9724 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
9725 return true;
9726 }
9727 }
9728 return false;
9729 case "array":
9730 for ( var i = 0; i < obj.length; i++) {
9731 if (search(obj[i], text)) {
9732 return true;
9733 }
9734 }
9735 return false;
9736 default:
9737 return false;
9738 }
9739 };
9740 switch (typeof expression) {
9741 case "boolean":
9742 case "number":
9743 case "string":
9744 expression = {$:expression};
9745 case "object":
9746 for (var key in expression) {
9747 if (key == '$') {
9748 (function() {
9749 var text = (''+expression[key]).toLowerCase();
9750 if (!text) return;
9751 predicates.push(function(value) {
9752 return search(value, text);
9753 });
9754 })();
9755 } else {
9756 (function() {
9757 var path = key;
9758 var text = (''+expression[key]).toLowerCase();
9759 if (!text) return;
9760 predicates.push(function(value) {
9761 return search(getter(value, path), text);
9762 });
9763 })();
9764 }
9765 }
9766 break;
9767 case 'function':
9768 predicates.push(expression);
9769 break;
9770 default:
9771 return array;
9772 }
9773 var filtered = [];
9774 for ( var j = 0; j < array.length; j++) {
9775 var value = array[j];
9776 if (predicates.check(value)) {
9777 filtered.push(value);
9778 }
9779 }
9780 return filtered;
9781 }
9782 }
9783
9784 /**
9785 * @ngdoc filter
9786 * @name ng.filter:currency
9787 * @function
9788 *
9789 * @description
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.
9792 *
9793 * @param {number} amount Input to filter.
9794 * @param {string=} symbol Currency symbol or identifier to be displayed.
9795 * @returns {string} Formatted number.
9796 *
9797 *
9798 * @example
9799 <doc:example>
9800 <doc:source>
9801 <script>
9802 function Ctrl($scope) {
9803 $scope.amount = 1234.56;
9804 }
9805 </script>
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$"}}
9810 </div>
9811 </doc:source>
9812 <doc:scenario>
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');
9816 });
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)');
9821 });
9822 </doc:scenario>
9823 </doc:example>
9824 */
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);
9832 };
9833 }
9834
9835 /**
9836 * @ngdoc filter
9837 * @name ng.filter:number
9838 * @function
9839 *
9840 * @description
9841 * Formats a number as text.
9842 *
9843 * If the input is not a number an empty string is returned.
9844 *
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.
9848 *
9849 * @example
9850 <doc:example>
9851 <doc:source>
9852 <script>
9853 function Ctrl($scope) {
9854 $scope.val = 1234.56789;
9855 }
9856 </script>
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}}
9862 </div>
9863 </doc:source>
9864 <doc:scenario>
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');
9869 });
9870
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');
9876 });
9877 </doc:scenario>
9878 </doc:example>
9879 */
9880
9881
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,
9887 fractionSize);
9888 };
9889 }
9890
9891 var DECIMAL_SEP = '.';
9892 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
9893 if (isNaN(number) || !isFinite(number)) return '';
9894
9895 var isNegative = number < 0;
9896 number = Math.abs(number);
9897 var numStr = number + '',
9898 formatedText = '',
9899 parts = [];
9900
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) {
9905 numStr = '0';
9906 } else {
9907 formatedText = numStr;
9908 hasExponent = true;
9909 }
9910 }
9911
9912 if (!hasExponent) {
9913 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
9914
9915 // determine fractionSize if it is not specified
9916 if (isUndefined(fractionSize)) {
9917 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
9918 }
9919
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] || '';
9925
9926 var pos = 0,
9927 lgroup = pattern.lgSize,
9928 group = pattern.gSize;
9929
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;
9935 }
9936 formatedText += whole.charAt(i);
9937 }
9938 }
9939
9940 for (i = pos; i < whole.length; i++) {
9941 if ((whole.length - i)%lgroup === 0 && i !== 0) {
9942 formatedText += groupSep;
9943 }
9944 formatedText += whole.charAt(i);
9945 }
9946
9947 // format fraction part.
9948 while(fraction.length < fractionSize) {
9949 fraction += '0';
9950 }
9951
9952 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
9953 }
9954
9955 parts.push(isNegative ? pattern.negPre : pattern.posPre);
9956 parts.push(formatedText);
9957 parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
9958 return parts.join('');
9959 }
9960
9961 function padNumber(num, digits, trim) {
9962 var neg = '';
9963 if (num < 0) {
9964 neg = '-';
9965 num = -num;
9966 }
9967 num = '' + num;
9968 while(num.length < digits) num = '0' + num;
9969 if (trim)
9970 num = num.substr(num.length - digits);
9971 return neg + num;
9972 }
9973
9974
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)
9980 value += offset;
9981 if (value === 0 && offset == -12 ) value = 12;
9982 return padNumber(value, size, trim);
9983 };
9984 }
9985
9986 function dateStrGetter(name, shortForm) {
9987 return function(date, formats) {
9988 var value = date['get' + name]();
9989 var get = uppercase(shortForm ? ('SHORT' + name) : name);
9990
9991 return formats[get][value];
9992 };
9993 }
9994
9995 function timeZoneGetter(date) {
9996 var zone = -1 * date.getTimezoneOffset();
9997 var paddedZone = (zone >= 0) ? "+" : "";
9998
9999 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
10000 padNumber(Math.abs(zone % 60), 2);
10001
10002 return paddedZone;
10003 }
10004
10005 function ampmGetter(date, formats) {
10006 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
10007 }
10008
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),
10029 a: ampmGetter,
10030 Z: timeZoneGetter
10031 };
10032
10033 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
10034 NUMBER_STRING = /^\d+$/;
10035
10036 /**
10037 * @ngdoc filter
10038 * @name ng.filter:date
10039 * @function
10040 *
10041 * @description
10042 * Formats `date` to a string based on the requested `format`.
10043 *
10044 * `format` string can be composed of the following elements:
10045 *
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)
10067 *
10068 * `format` string can also be one of the following predefined
10069 * {@link guide/i18n localizable formats}:
10070 *
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)
10081 *
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"`).
10085 *
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.
10093 *
10094 * @example
10095 <doc:example>
10096 <doc:source>
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>
10103 </doc:source>
10104 <doc:scenario>
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)/);
10112 });
10113 </doc:scenario>
10114 </doc:example>
10115 */
10116 dateFilter.$inject = ['$locale'];
10117 function dateFilter($locale) {
10118
10119
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){
10122 var match;
10123 if (match = string.match(R_ISO8601_STR)) {
10124 var date = new Date(0),
10125 tzHour = 0,
10126 tzMin = 0;
10127 if (match[9]) {
10128 tzHour = int(match[9] + match[10]);
10129 tzMin = int(match[9] + match[11]);
10130 }
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));
10133 return date;
10134 }
10135 return string;
10136 }
10137
10138
10139 return function(date, format) {
10140 var text = '',
10141 parts = [],
10142 fn, match;
10143
10144 format = format || 'mediumDate';
10145 format = $locale.DATETIME_FORMATS[format] || format;
10146 if (isString(date)) {
10147 if (NUMBER_STRING.test(date)) {
10148 date = int(date);
10149 } else {
10150 date = jsonStringToDate(date);
10151 }
10152 }
10153
10154 if (isNumber(date)) {
10155 date = new Date(date);
10156 }
10157
10158 if (!isDate(date)) {
10159 return date;
10160 }
10161
10162 while(format) {
10163 match = DATE_FORMATS_SPLIT.exec(format);
10164 if (match) {
10165 parts = concat(parts, match, 1);
10166 format = parts.pop();
10167 } else {
10168 parts.push(format);
10169 format = null;
10170 }
10171 }
10172
10173 forEach(parts, function(value){
10174 fn = DATE_FORMATS[value];
10175 text += fn ? fn(date, $locale.DATETIME_FORMATS)
10176 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
10177 });
10178
10179 return text;
10180 };
10181 }
10182
10183
10184 /**
10185 * @ngdoc filter
10186 * @name ng.filter:json
10187 * @function
10188 *
10189 * @description
10190 * Allows you to convert a JavaScript object into JSON string.
10191 *
10192 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
10193 * the binding is automatically converted to JSON.
10194 *
10195 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
10196 * @returns {string} JSON string.
10197 *
10198 *
10199 * @example:
10200 <doc:example>
10201 <doc:source>
10202 <pre>{{ {'name':'value'} | json }}</pre>
10203 </doc:source>
10204 <doc:scenario>
10205 it('should jsonify filtered objects', function() {
10206 expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
10207 });
10208 </doc:scenario>
10209 </doc:example>
10210 *
10211 */
10212 function jsonFilter() {
10213 return function(object) {
10214 return toJson(object, true);
10215 };
10216 }
10217
10218
10219 /**
10220 * @ngdoc filter
10221 * @name ng.filter:lowercase
10222 * @function
10223 * @description
10224 * Converts string to lowercase.
10225 * @see angular.lowercase
10226 */
10227 var lowercaseFilter = valueFn(lowercase);
10228
10229
10230 /**
10231 * @ngdoc filter
10232 * @name ng.filter:uppercase
10233 * @function
10234 * @description
10235 * Converts string to uppercase.
10236 * @see angular.uppercase
10237 */
10238 var uppercaseFilter = valueFn(uppercase);
10239
10240 /**
10241 * @ngdoc function
10242 * @name ng.filter:limitTo
10243 * @function
10244 *
10245 * @description
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`.
10249 *
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.
10252 *
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`
10259 * elements.
10260 *
10261 * @example
10262 <doc:example>
10263 <doc:source>
10264 <script>
10265 function Ctrl($scope) {
10266 $scope.numbers = [1,2,3,4,5,6,7,8,9];
10267 $scope.limit = 3;
10268 }
10269 </script>
10270 <div ng-controller="Ctrl">
10271 Limit {{numbers}} to: <input type="integer" ng-model="limit">
10272 <p>Output: {{ numbers | limitTo:limit }}</p>
10273 </div>
10274 </doc:source>
10275 <doc:scenario>
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]');
10279 });
10280
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]');
10284 });
10285
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]');
10289 });
10290 </doc:scenario>
10291 </doc:example>
10292 */
10293 function limitToFilter(){
10294 return function(array, limit) {
10295 if (!(array instanceof Array)) return array;
10296 limit = int(limit);
10297 var out = [],
10298 i, n;
10299
10300 // check that array is iterable
10301 if (!array || !(array instanceof Array))
10302 return out;
10303
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;
10309
10310 if (limit > 0) {
10311 i = 0;
10312 n = limit;
10313 } else {
10314 i = array.length + limit;
10315 n = array.length;
10316 }
10317
10318 for (; i<n; i++) {
10319 out.push(array[i]);
10320 }
10321
10322 return out;
10323 }
10324 }
10325
10326 /**
10327 * @ngdoc function
10328 * @name ng.filter:orderBy
10329 * @function
10330 *
10331 * @description
10332 * Orders a specified `array` by the `expression` predicate.
10333 *
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.
10336 *
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.
10340 *
10341 * Can be one of:
10342 *
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.
10350 *
10351 * @param {boolean=} reverse Reverse the order the array.
10352 * @returns {Array} Sorted copy of the source array.
10353 *
10354 * @example
10355 <doc:example>
10356 <doc:source>
10357 <script>
10358 function Ctrl($scope) {
10359 $scope.friends =
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';
10366 }
10367 </script>
10368 <div ng-controller="Ctrl">
10369 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
10370 <hr/>
10371 [ <a href="" ng-click="predicate=''">unsorted</a> ]
10372 <table class="friend">
10373 <tr>
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>
10378 </tr>
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>
10383 </tr>
10384 </table>
10385 </div>
10386 </doc:source>
10387 <doc:scenario>
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']);
10394 });
10395
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']);
10402
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']);
10408 });
10409 </doc:scenario>
10410 </doc:example>
10411 */
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);
10424 }
10425 get = $parse(predicate);
10426 }
10427 return reverseComparator(function(a,b){
10428 return compare(get(a),get(b));
10429 }, descending);
10430 });
10431 var arrayCopy = [];
10432 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
10433 return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
10434
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;
10439 }
10440 return 0;
10441 }
10442 function reverseComparator(comp, descending) {
10443 return toBoolean(descending)
10444 ? function(a,b){return comp(b,a);}
10445 : comp;
10446 }
10447 function compare(v1, v2){
10448 var t1 = typeof v1;
10449 var t2 = typeof v2;
10450 if (t1 == t2) {
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;
10455 } else {
10456 return t1 < t2 ? -1 : 1;
10457 }
10458 }
10459 }
10460 }
10461
10462 function ngDirective(directive) {
10463 if (isFunction(directive)) {
10464 directive = {
10465 link: directive
10466 }
10467 }
10468 directive.restrict = directive.restrict || 'AC';
10469 return valueFn(directive);
10470 }
10471
10472 /**
10473 * @ngdoc directive
10474 * @name ng.directive:a
10475 * @restrict E
10476 *
10477 * @description
10478 * Modifies the default behavior of html A tag, so that the default action is prevented when href
10479 * attribute is empty.
10480 *
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>`
10484 */
10485 var htmlAnchorDirective = valueFn({
10486 restrict: 'E',
10487 compile: function(element, attr) {
10488
10489 if (msie <= 8) {
10490
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', '');
10495 }
10496
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 @
10500 // see issue #1949
10501 element.append(document.createComment('IE fix'));
10502 }
10503
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();
10509 }
10510 });
10511 }
10512 }
10513 });
10514
10515 /**
10516 * @ngdoc directive
10517 * @name ng.directive:ngHref
10518 * @restrict A
10519 *
10520 * @description
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.
10526 *
10527 * The buggy way to write it:
10528 * <pre>
10529 * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
10530 * </pre>
10531 *
10532 * The correct way to write it:
10533 * <pre>
10534 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
10535 * </pre>
10536 *
10537 * @element A
10538 * @param {template} ngHref any string which can contain `{{}}` markup.
10539 *
10540 * @example
10541 * This example uses `link` variable inside `href` attribute:
10542 <doc:example>
10543 <doc:source>
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)
10551 </doc:source>
10552 <doc:scenario>
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("");
10557 });
10558
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("");
10563 });
10564
10565 it('should execute ng-click and change url when ng-href specified', function() {
10566 expect(element('#link-3').attr('href')).toBe("/123");
10567
10568 element('#link-3').click();
10569 expect(browser().window().path()).toEqual('/123');
10570 });
10571
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('');
10576 });
10577
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);
10582 });
10583
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');
10587
10588 element('#link-6').click();
10589 expect(browser().location().url()).toEqual('/6');
10590 });
10591 </doc:scenario>
10592 </doc:example>
10593 */
10594
10595 /**
10596 * @ngdoc directive
10597 * @name ng.directive:ngSrc
10598 * @restrict A
10599 *
10600 * @description
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.
10605 *
10606 * The buggy way to write it:
10607 * <pre>
10608 * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
10609 * </pre>
10610 *
10611 * The correct way to write it:
10612 * <pre>
10613 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
10614 * </pre>
10615 *
10616 * @element IMG
10617 * @param {template} ngSrc any string which can contain `{{}}` markup.
10618 */
10619
10620 /**
10621 * @ngdoc directive
10622 * @name ng.directive:ngDisabled
10623 * @restrict A
10624 *
10625 * @description
10626 *
10627 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
10628 * <pre>
10629 * <div ng-init="scope = { isDisabled: false }">
10630 * <button disabled="{{scope.isDisabled}}">Disabled</button>
10631 * </div>
10632 * </pre>
10633 *
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.
10638 *
10639 * @example
10640 <doc:example>
10641 <doc:source>
10642 Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
10643 <button ng-model="button" ng-disabled="checked">Button</button>
10644 </doc:source>
10645 <doc:scenario>
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();
10650 });
10651 </doc:scenario>
10652 </doc:example>
10653 *
10654 * @element INPUT
10655 * @param {expression} ngDisabled Angular expression that will be evaluated.
10656 */
10657
10658
10659 /**
10660 * @ngdoc directive
10661 * @name ng.directive:ngChecked
10662 * @restrict A
10663 *
10664 * @description
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.
10669 * @example
10670 <doc:example>
10671 <doc:source>
10672 Check me to check both: <input type="checkbox" ng-model="master"><br/>
10673 <input id="checkSlave" type="checkbox" ng-checked="master">
10674 </doc:source>
10675 <doc:scenario>
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();
10680 });
10681 </doc:scenario>
10682 </doc:example>
10683 *
10684 * @element INPUT
10685 * @param {expression} ngChecked Angular expression that will be evaluated.
10686 */
10687
10688
10689 /**
10690 * @ngdoc directive
10691 * @name ng.directive:ngMultiple
10692 * @restrict A
10693 *
10694 * @description
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.
10699 *
10700 * @example
10701 <doc:example>
10702 <doc:source>
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>
10709 </select>
10710 </doc:source>
10711 <doc:scenario>
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();
10716 });
10717 </doc:scenario>
10718 </doc:example>
10719 *
10720 * @element SELECT
10721 * @param {expression} ngMultiple Angular expression that will be evaluated.
10722 */
10723
10724
10725 /**
10726 * @ngdoc directive
10727 * @name ng.directive:ngReadonly
10728 * @restrict A
10729 *
10730 * @description
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.
10735 * @example
10736 <doc:example>
10737 <doc:source>
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"/>
10740 </doc:source>
10741 <doc:scenario>
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();
10746 });
10747 </doc:scenario>
10748 </doc:example>
10749 *
10750 * @element INPUT
10751 * @param {string} expression Angular expression that will be evaluated.
10752 */
10753
10754
10755 /**
10756 * @ngdoc directive
10757 * @name ng.directive:ngSelected
10758 * @restrict A
10759 *
10760 * @description
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.
10765 * @example
10766 <doc:example>
10767 <doc:source>
10768 Check me to select: <input type="checkbox" ng-model="selected"><br/>
10769 <select>
10770 <option>Hello!</option>
10771 <option id="greet" ng-selected="selected">Greetings!</option>
10772 </select>
10773 </doc:source>
10774 <doc:scenario>
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();
10779 });
10780 </doc:scenario>
10781 </doc:example>
10782 *
10783 * @element OPTION
10784 * @param {string} expression Angular expression that will be evaluated.
10785 */
10786
10787
10788 var ngAttributeAliasDirectives = {};
10789
10790
10791 // boolean attrs are evaluated
10792 forEach(BOOLEAN_ATTR, function(propName, attrName) {
10793 var normalized = directiveNormalize('ng-' + attrName);
10794 ngAttributeAliasDirectives[normalized] = function() {
10795 return {
10796 priority: 100,
10797 compile: function() {
10798 return function(scope, element, attr) {
10799 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
10800 attr.$set(attrName, !!value);
10801 });
10802 };
10803 }
10804 };
10805 };
10806 });
10807
10808
10809 // ng-src, ng-href are interpolated
10810 forEach(['src', 'href'], function(attrName) {
10811 var normalized = directiveNormalize('ng-' + attrName);
10812 ngAttributeAliasDirectives[normalized] = function() {
10813 return {
10814 priority: 99, // it needs to run after the attributes are interpolated
10815 link: function(scope, element, attr) {
10816 attr.$observe(normalized, function(value) {
10817 if (!value)
10818 return;
10819
10820 attr.$set(attrName, value);
10821
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]);
10827 });
10828 }
10829 };
10830 };
10831 });
10832
10833 var nullFormCtrl = {
10834 $addControl: noop,
10835 $removeControl: noop,
10836 $setValidity: noop,
10837 $setDirty: noop
10838 };
10839
10840 /**
10841 * @ngdoc object
10842 * @name ng.directive:form.FormController
10843 *
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.
10848 *
10849 * @property {Object} $error Is an object hash, containing references to all invalid controls or
10850 * forms, where:
10851 *
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.
10854 *
10855 * @description
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.
10858 *
10859 * Each {@link ng.directive:form form} directive creates an instance
10860 * of `FormController`.
10861 *
10862 */
10863 //asks for $scope to fool the BC controller module
10864 FormController.$inject = ['$element', '$attrs', '$scope'];
10865 function FormController(element, attrs) {
10866 var form = this,
10867 parentForm = element.parent().controller('form') || nullFormCtrl,
10868 invalidCount = 0, // used to easily determine if we are valid
10869 errors = form.$error = {};
10870
10871 // init state
10872 form.$name = attrs.name;
10873 form.$dirty = false;
10874 form.$pristine = true;
10875 form.$valid = true;
10876 form.$invalid = false;
10877
10878 parentForm.$addControl(form);
10879
10880 // Setup initial state of the control
10881 element.addClass(PRISTINE_CLASS);
10882 toggleValidCss(true);
10883
10884 // convenience method for easy toggling of classes
10885 function toggleValidCss(isValid, validationErrorKey) {
10886 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
10887 element.
10888 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
10889 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
10890 }
10891
10892 form.$addControl = function(control) {
10893 if (control.$name && !form.hasOwnProperty(control.$name)) {
10894 form[control.$name] = control;
10895 }
10896 };
10897
10898 form.$removeControl = function(control) {
10899 if (control.$name && form[control.$name] === control) {
10900 delete form[control.$name];
10901 }
10902 forEach(errors, function(queue, validationToken) {
10903 form.$setValidity(validationToken, true, control);
10904 });
10905 };
10906
10907 form.$setValidity = function(validationToken, isValid, control) {
10908 var queue = errors[validationToken];
10909
10910 if (isValid) {
10911 if (queue) {
10912 arrayRemove(queue, control);
10913 if (!queue.length) {
10914 invalidCount--;
10915 if (!invalidCount) {
10916 toggleValidCss(isValid);
10917 form.$valid = true;
10918 form.$invalid = false;
10919 }
10920 errors[validationToken] = false;
10921 toggleValidCss(true, validationToken);
10922 parentForm.$setValidity(validationToken, true, form);
10923 }
10924 }
10925
10926 } else {
10927 if (!invalidCount) {
10928 toggleValidCss(isValid);
10929 }
10930 if (queue) {
10931 if (includes(queue, control)) return;
10932 } else {
10933 errors[validationToken] = queue = [];
10934 invalidCount++;
10935 toggleValidCss(false, validationToken);
10936 parentForm.$setValidity(validationToken, false, form);
10937 }
10938 queue.push(control);
10939
10940 form.$valid = false;
10941 form.$invalid = true;
10942 }
10943 };
10944
10945 form.$setDirty = function() {
10946 element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
10947 form.$dirty = true;
10948 form.$pristine = false;
10949 parentForm.$setDirty();
10950 };
10951
10952 }
10953
10954
10955 /**
10956 * @ngdoc directive
10957 * @name ng.directive:ngForm
10958 * @restrict EAC
10959 *
10960 * @description
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.
10964 *
10965 * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into
10966 * related scope, under this name.
10967 *
10968 */
10969
10970 /**
10971 * @ngdoc directive
10972 * @name ng.directive:form
10973 * @restrict E
10974 *
10975 * @description
10976 * Directive that instantiates
10977 * {@link ng.directive:form.FormController FormController}.
10978 *
10979 * If `name` attribute is specified, the form controller is published onto the current scope under
10980 * this name.
10981 *
10982 * # Alias: {@link ng.directive:ngForm `ngForm`}
10983 *
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.
10988 *
10989 *
10990 * # CSS classes
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.
10995 *
10996 *
10997 * # Submitting a form and preventing default action
10998 *
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.
11003 *
11004 * For this reason, Angular prevents the default action (form submission to the server) unless the
11005 * `<form>` element has an `action` attribute specified.
11006 *
11007 * You can use one of the following two ways to specify what javascript method should be called when
11008 * a form is submitted:
11009 *
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])
11013 *
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:
11016 *
11017 * - If a form has only one input field then hitting enter in this field triggers form submit
11018 * (`ngSubmit`)
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`)
11024 *
11025 * @param {string=} name Name of the form. If specified, the form controller will be published into
11026 * related scope, under this name.
11027 *
11028 * @example
11029 <doc:example>
11030 <doc:source>
11031 <script>
11032 function Ctrl($scope) {
11033 $scope.userType = 'guest';
11034 }
11035 </script>
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>
11044 </form>
11045 </doc:source>
11046 <doc:scenario>
11047 it('should initialize to model', function() {
11048 expect(binding('userType')).toEqual('guest');
11049 expect(binding('myForm.input.$valid')).toEqual('true');
11050 });
11051
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');
11056 });
11057 </doc:scenario>
11058 </doc:example>
11059 */
11060 var formDirectiveFactory = function(isNgForm) {
11061 return ['$timeout', function($timeout) {
11062 var formDirective = {
11063 name: 'form',
11064 restrict: 'E',
11065 controller: FormController,
11066 compile: function() {
11067 return {
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
11072 //
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
11080 };
11081
11082 addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
11083
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);
11089 }, 0, false);
11090 });
11091 }
11092
11093 var parentFormCtrl = formElement.parent().controller('form'),
11094 alias = attr.name || attr.ngForm;
11095
11096 if (alias) {
11097 scope[alias] = controller;
11098 }
11099 if (parentFormCtrl) {
11100 formElement.bind('$destroy', function() {
11101 parentFormCtrl.$removeControl(controller);
11102 if (alias) {
11103 scope[alias] = undefined;
11104 }
11105 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
11106 });
11107 }
11108 }
11109 };
11110 }
11111 };
11112
11113 return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective;
11114 }];
11115 };
11116
11117 var formDirective = formDirectiveFactory();
11118 var ngFormDirective = formDirectiveFactory(true);
11119
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*$/;
11123
11124 var inputType = {
11125
11126 /**
11127 * @ngdoc inputType
11128 * @name ng.directive:input.text
11129 *
11130 * @description
11131 * Standard HTML text input with angular data binding.
11132 *
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
11140 * minlength.
11141 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11142 * maxlength.
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.
11148 *
11149 * @example
11150 <doc:example>
11151 <doc:source>
11152 <script>
11153 function Ctrl($scope) {
11154 $scope.text = 'guest';
11155 $scope.word = /^\w*$/;
11156 }
11157 </script>
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">
11162 Required!</span>
11163 <span class="error" ng-show="myForm.input.$error.pattern">
11164 Single word only!</span>
11165
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/>
11171 </form>
11172 </doc:source>
11173 <doc:scenario>
11174 it('should initialize to model', function() {
11175 expect(binding('text')).toEqual('guest');
11176 expect(binding('myForm.input.$valid')).toEqual('true');
11177 });
11178
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');
11183 });
11184
11185 it('should be invalid if multi word', function() {
11186 input('text').enter('hello world');
11187 expect(binding('myForm.input.$valid')).toEqual('false');
11188 });
11189 </doc:scenario>
11190 </doc:example>
11191 */
11192 'text': textInputType,
11193
11194
11195 /**
11196 * @ngdoc inputType
11197 * @name ng.directive:input.number
11198 *
11199 * @description
11200 * Text input with number validation and transformation. Sets the `number` validation
11201 * error if not a valid number.
11202 *
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
11212 * minlength.
11213 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11214 * maxlength.
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.
11220 *
11221 * @example
11222 <doc:example>
11223 <doc:source>
11224 <script>
11225 function Ctrl($scope) {
11226 $scope.value = 12;
11227 }
11228 </script>
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">
11233 Required!</span>
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/>
11241 </form>
11242 </doc:source>
11243 <doc:scenario>
11244 it('should initialize to model', function() {
11245 expect(binding('value')).toEqual('12');
11246 expect(binding('myForm.input.$valid')).toEqual('true');
11247 });
11248
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');
11253 });
11254
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');
11259 });
11260 </doc:scenario>
11261 </doc:example>
11262 */
11263 'number': numberInputType,
11264
11265
11266 /**
11267 * @ngdoc inputType
11268 * @name ng.directive:input.url
11269 *
11270 * @description
11271 * Text input with URL validation. Sets the `url` validation error key if the content is not a
11272 * valid URL.
11273 *
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
11281 * minlength.
11282 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11283 * maxlength.
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.
11289 *
11290 * @example
11291 <doc:example>
11292 <doc:source>
11293 <script>
11294 function Ctrl($scope) {
11295 $scope.text = 'http://google.com';
11296 }
11297 </script>
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">
11301 Required!</span>
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/>
11310 </form>
11311 </doc:source>
11312 <doc:scenario>
11313 it('should initialize to model', function() {
11314 expect(binding('text')).toEqual('http://google.com');
11315 expect(binding('myForm.input.$valid')).toEqual('true');
11316 });
11317
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');
11322 });
11323
11324 it('should be invalid if not url', function() {
11325 input('text').enter('xxx');
11326 expect(binding('myForm.input.$valid')).toEqual('false');
11327 });
11328 </doc:scenario>
11329 </doc:example>
11330 */
11331 'url': urlInputType,
11332
11333
11334 /**
11335 * @ngdoc inputType
11336 * @name ng.directive:input.email
11337 *
11338 * @description
11339 * Text input with email validation. Sets the `email` validation error key if not a valid email
11340 * address.
11341 *
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
11349 * minlength.
11350 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11351 * maxlength.
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.
11355 *
11356 * @example
11357 <doc:example>
11358 <doc:source>
11359 <script>
11360 function Ctrl($scope) {
11361 $scope.text = 'me@example.com';
11362 }
11363 </script>
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">
11367 Required!</span>
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/>
11376 </form>
11377 </doc:source>
11378 <doc:scenario>
11379 it('should initialize to model', function() {
11380 expect(binding('text')).toEqual('me@example.com');
11381 expect(binding('myForm.input.$valid')).toEqual('true');
11382 });
11383
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');
11388 });
11389
11390 it('should be invalid if not email', function() {
11391 input('text').enter('xxx');
11392 expect(binding('myForm.input.$valid')).toEqual('false');
11393 });
11394 </doc:scenario>
11395 </doc:example>
11396 */
11397 'email': emailInputType,
11398
11399
11400 /**
11401 * @ngdoc inputType
11402 * @name ng.directive:input.radio
11403 *
11404 * @description
11405 * HTML radio button.
11406 *
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.
11412 *
11413 * @example
11414 <doc:example>
11415 <doc:source>
11416 <script>
11417 function Ctrl($scope) {
11418 $scope.color = 'blue';
11419 }
11420 </script>
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/>
11426 </form>
11427 </doc:source>
11428 <doc:scenario>
11429 it('should change state', function() {
11430 expect(binding('color')).toEqual('blue');
11431
11432 input('color').select('red');
11433 expect(binding('color')).toEqual('red');
11434 });
11435 </doc:scenario>
11436 </doc:example>
11437 */
11438 'radio': radioInputType,
11439
11440
11441 /**
11442 * @ngdoc inputType
11443 * @name ng.directive:input.checkbox
11444 *
11445 * @description
11446 * HTML checkbox.
11447 *
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.
11454 *
11455 * @example
11456 <doc:example>
11457 <doc:source>
11458 <script>
11459 function Ctrl($scope) {
11460 $scope.value1 = true;
11461 $scope.value2 = 'YES'
11462 }
11463 </script>
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/>
11470 </form>
11471 </doc:source>
11472 <doc:scenario>
11473 it('should change state', function() {
11474 expect(binding('value1')).toEqual('true');
11475 expect(binding('value2')).toEqual('YES');
11476
11477 input('value1').check();
11478 input('value2').check();
11479 expect(binding('value1')).toEqual('false');
11480 expect(binding('value2')).toEqual('NO');
11481 });
11482 </doc:scenario>
11483 </doc:example>
11484 */
11485 'checkbox': checkboxInputType,
11486
11487 'hidden': noop,
11488 'button': noop,
11489 'submit': noop,
11490 'reset': noop
11491 };
11492
11493
11494 function isEmpty(value) {
11495 return isUndefined(value) || value === '' || value === null || value !== value;
11496 }
11497
11498
11499 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11500
11501 var listener = function() {
11502 var value = trim(element.val());
11503
11504 if (ctrl.$viewValue !== value) {
11505 scope.$apply(function() {
11506 ctrl.$setViewValue(value);
11507 });
11508 }
11509 };
11510
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);
11515 } else {
11516 var timeout;
11517
11518 var deferListener = function() {
11519 if (!timeout) {
11520 timeout = $browser.defer(function() {
11521 listener();
11522 timeout = null;
11523 });
11524 }
11525 };
11526
11527 element.bind('keydown', function(event) {
11528 var key = event.keyCode;
11529
11530 // ignore
11531 // command modifiers arrows
11532 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
11533
11534 deferListener();
11535 });
11536
11537 // if user paste into input using mouse, we need "change" event to catch it
11538 element.bind('change', listener);
11539
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);
11543 }
11544 }
11545
11546
11547 ctrl.$render = function() {
11548 element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
11549 };
11550
11551 // pattern validator
11552 var pattern = attr.ngPattern,
11553 patternValidator;
11554
11555 var validate = function(regexp, value) {
11556 if (isEmpty(value) || regexp.test(value)) {
11557 ctrl.$setValidity('pattern', true);
11558 return value;
11559 } else {
11560 ctrl.$setValidity('pattern', false);
11561 return undefined;
11562 }
11563 };
11564
11565 if (pattern) {
11566 if (pattern.match(/^\/(.*)\/$/)) {
11567 pattern = new RegExp(pattern.substr(1, pattern.length - 2));
11568 patternValidator = function(value) {
11569 return validate(pattern, value)
11570 };
11571 } else {
11572 patternValidator = function(value) {
11573 var patternObj = scope.$eval(pattern);
11574
11575 if (!patternObj || !patternObj.test) {
11576 throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj);
11577 }
11578 return validate(patternObj, value);
11579 };
11580 }
11581
11582 ctrl.$formatters.push(patternValidator);
11583 ctrl.$parsers.push(patternValidator);
11584 }
11585
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);
11592 return undefined;
11593 } else {
11594 ctrl.$setValidity('minlength', true);
11595 return value;
11596 }
11597 };
11598
11599 ctrl.$parsers.push(minLengthValidator);
11600 ctrl.$formatters.push(minLengthValidator);
11601 }
11602
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);
11609 return undefined;
11610 } else {
11611 ctrl.$setValidity('maxlength', true);
11612 return value;
11613 }
11614 };
11615
11616 ctrl.$parsers.push(maxLengthValidator);
11617 ctrl.$formatters.push(maxLengthValidator);
11618 }
11619 }
11620
11621 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11622 textInputType(scope, element, attr, ctrl, $sniffer, $browser);
11623
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));
11629 } else {
11630 ctrl.$setValidity('number', false);
11631 return undefined;
11632 }
11633 });
11634
11635 ctrl.$formatters.push(function(value) {
11636 return isEmpty(value) ? '' : '' + value;
11637 });
11638
11639 if (attr.min) {
11640 var min = parseFloat(attr.min);
11641 var minValidator = function(value) {
11642 if (!isEmpty(value) && value < min) {
11643 ctrl.$setValidity('min', false);
11644 return undefined;
11645 } else {
11646 ctrl.$setValidity('min', true);
11647 return value;
11648 }
11649 };
11650
11651 ctrl.$parsers.push(minValidator);
11652 ctrl.$formatters.push(minValidator);
11653 }
11654
11655 if (attr.max) {
11656 var max = parseFloat(attr.max);
11657 var maxValidator = function(value) {
11658 if (!isEmpty(value) && value > max) {
11659 ctrl.$setValidity('max', false);
11660 return undefined;
11661 } else {
11662 ctrl.$setValidity('max', true);
11663 return value;
11664 }
11665 };
11666
11667 ctrl.$parsers.push(maxValidator);
11668 ctrl.$formatters.push(maxValidator);
11669 }
11670
11671 ctrl.$formatters.push(function(value) {
11672
11673 if (isEmpty(value) || isNumber(value)) {
11674 ctrl.$setValidity('number', true);
11675 return value;
11676 } else {
11677 ctrl.$setValidity('number', false);
11678 return undefined;
11679 }
11680 });
11681 }
11682
11683 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11684 textInputType(scope, element, attr, ctrl, $sniffer, $browser);
11685
11686 var urlValidator = function(value) {
11687 if (isEmpty(value) || URL_REGEXP.test(value)) {
11688 ctrl.$setValidity('url', true);
11689 return value;
11690 } else {
11691 ctrl.$setValidity('url', false);
11692 return undefined;
11693 }
11694 };
11695
11696 ctrl.$formatters.push(urlValidator);
11697 ctrl.$parsers.push(urlValidator);
11698 }
11699
11700 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11701 textInputType(scope, element, attr, ctrl, $sniffer, $browser);
11702
11703 var emailValidator = function(value) {
11704 if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
11705 ctrl.$setValidity('email', true);
11706 return value;
11707 } else {
11708 ctrl.$setValidity('email', false);
11709 return undefined;
11710 }
11711 };
11712
11713 ctrl.$formatters.push(emailValidator);
11714 ctrl.$parsers.push(emailValidator);
11715 }
11716
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());
11721 }
11722
11723 element.bind('click', function() {
11724 if (element[0].checked) {
11725 scope.$apply(function() {
11726 ctrl.$setViewValue(attr.value);
11727 });
11728 }
11729 });
11730
11731 ctrl.$render = function() {
11732 var value = attr.value;
11733 element[0].checked = (value == ctrl.$viewValue);
11734 };
11735
11736 attr.$observe('value', ctrl.$render);
11737 }
11738
11739 function checkboxInputType(scope, element, attr, ctrl) {
11740 var trueValue = attr.ngTrueValue,
11741 falseValue = attr.ngFalseValue;
11742
11743 if (!isString(trueValue)) trueValue = true;
11744 if (!isString(falseValue)) falseValue = false;
11745
11746 element.bind('click', function() {
11747 scope.$apply(function() {
11748 ctrl.$setViewValue(element[0].checked);
11749 });
11750 });
11751
11752 ctrl.$render = function() {
11753 element[0].checked = ctrl.$viewValue;
11754 };
11755
11756 ctrl.$formatters.push(function(value) {
11757 return value === trueValue;
11758 });
11759
11760 ctrl.$parsers.push(function(value) {
11761 return value ? trueValue : falseValue;
11762 });
11763 }
11764
11765
11766 /**
11767 * @ngdoc directive
11768 * @name ng.directive:textarea
11769 * @restrict E
11770 *
11771 * @description
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}.
11775 *
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
11783 * minlength.
11784 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11785 * maxlength.
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.
11791 */
11792
11793
11794 /**
11795 * @ngdoc directive
11796 * @name ng.directive:input
11797 * @restrict E
11798 *
11799 * @description
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.
11802 *
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
11808 * minlength.
11809 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
11810 * maxlength.
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.
11816 *
11817 * @example
11818 <doc:example>
11819 <doc:source>
11820 <script>
11821 function Ctrl($scope) {
11822 $scope.user = {name: 'guest', last: 'visitor'};
11823 }
11824 </script>
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">
11833 Too short!</span>
11834 <span class="error" ng-show="myForm.lastName.$error.maxlength">
11835 Too long!</span><br>
11836 </form>
11837 <hr>
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>
11847 </div>
11848 </doc:source>
11849 <doc:scenario>
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');
11854 });
11855
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');
11861 });
11862
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');
11868 });
11869
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');
11876 });
11877
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');
11885 });
11886 </doc:scenario>
11887 </doc:example>
11888 */
11889 var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
11890 return {
11891 restrict: 'E',
11892 require: '?ngModel',
11893 link: function(scope, element, attr, ctrl) {
11894 if (ctrl) {
11895 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
11896 $browser);
11897 }
11898 }
11899 };
11900 }];
11901
11902 var VALID_CLASS = 'ng-valid',
11903 INVALID_CLASS = 'ng-invalid',
11904 PRISTINE_CLASS = 'ng-pristine',
11905 DIRTY_CLASS = 'ng-dirty';
11906
11907 /**
11908 * @ngdoc object
11909 * @name ng.directive:ngModel.NgModelController
11910 *
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.
11915 *
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.
11918 *
11919 * @property {Object} $error An bject hash with all errors as keys.
11920 *
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.
11925 *
11926 * @description
11927 *
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.
11933 *
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.
11937 *
11938 * <example module="customControl">
11939 <file name="style.css">
11940 [contenteditable] {
11941 border: 1px solid black;
11942 background-color: white;
11943 min-height: 20px;
11944 }
11945
11946 .ng-invalid {
11947 border: 1px solid red;
11948 }
11949
11950 </file>
11951 <file name="script.js">
11952 angular.module('customControl', []).
11953 directive('contenteditable', function() {
11954 return {
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
11959
11960 // Specify how UI should be updated
11961 ngModel.$render = function() {
11962 element.html(ngModel.$viewValue || '');
11963 };
11964
11965 // Listen for change events to enable binding
11966 element.bind('blur keyup change', function() {
11967 scope.$apply(read);
11968 });
11969 read(); // initialize
11970
11971 // Write data to the model
11972 function read() {
11973 ngModel.$setViewValue(element.html());
11974 }
11975 }
11976 };
11977 });
11978 </file>
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>
11985 <hr>
11986 <textarea ng-model="userContent"></textarea>
11987 </form>
11988 </file>
11989 <file name="scenario.js">
11990 it('should data-bind and become invalid', function() {
11991 var contentEditable = element('[contenteditable]');
11992
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/);
11997 });
11998 </file>
11999 * </example>
12000 *
12001 */
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;
12014
12015 var ngModelGet = $parse($attr.ngModel),
12016 ngModelSet = ngModelGet.assign;
12017
12018 if (!ngModelSet) {
12019 throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
12020 ' (' + startingTag($element) + ')');
12021 }
12022
12023 /**
12024 * @ngdoc function
12025 * @name ng.directive:ngModel.NgModelController#$render
12026 * @methodOf ng.directive:ngModel.NgModelController
12027 *
12028 * @description
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.
12031 */
12032 this.$render = noop;
12033
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
12037
12038
12039 // Setup initial state of the control
12040 $element.addClass(PRISTINE_CLASS);
12041 toggleValidCss(true);
12042
12043 // convenience method for easy toggling of classes
12044 function toggleValidCss(isValid, validationErrorKey) {
12045 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
12046 $element.
12047 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
12048 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
12049 }
12050
12051 /**
12052 * @ngdoc function
12053 * @name ng.directive:ngModel.NgModelController#$setValidity
12054 * @methodOf ng.directive:ngModel.NgModelController
12055 *
12056 * @description
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).
12059 *
12060 * This method should be called by validators - i.e. the parser or formatter functions.
12061 *
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).
12068 */
12069 this.$setValidity = function(validationErrorKey, isValid) {
12070 if ($error[validationErrorKey] === !isValid) return;
12071
12072 if (isValid) {
12073 if ($error[validationErrorKey]) invalidCount--;
12074 if (!invalidCount) {
12075 toggleValidCss(true);
12076 this.$valid = true;
12077 this.$invalid = false;
12078 }
12079 } else {
12080 toggleValidCss(false);
12081 this.$invalid = true;
12082 this.$valid = false;
12083 invalidCount++;
12084 }
12085
12086 $error[validationErrorKey] = !isValid;
12087 toggleValidCss(isValid, validationErrorKey);
12088
12089 parentForm.$setValidity(validationErrorKey, isValid, this);
12090 };
12091
12092
12093 /**
12094 * @ngdoc function
12095 * @name ng.directive:ngModel.NgModelController#$setViewValue
12096 * @methodOf ng.directive:ngModel.NgModelController
12097 *
12098 * @description
12099 * Read a value from view.
12100 *
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.
12104 *
12105 * It internally calls all `parsers` and if resulted value is valid, updates the model and
12106 * calls all registered change listeners.
12107 *
12108 * @param {string} value Value from the view.
12109 */
12110 this.$setViewValue = function(value) {
12111 this.$viewValue = value;
12112
12113 // change to dirty
12114 if (this.$pristine) {
12115 this.$dirty = true;
12116 this.$pristine = false;
12117 $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
12118 parentForm.$setDirty();
12119 }
12120
12121 forEach(this.$parsers, function(fn) {
12122 value = fn(value);
12123 });
12124
12125 if (this.$modelValue !== value) {
12126 this.$modelValue = value;
12127 ngModelSet($scope, value);
12128 forEach(this.$viewChangeListeners, function(listener) {
12129 try {
12130 listener();
12131 } catch(e) {
12132 $exceptionHandler(e);
12133 }
12134 })
12135 }
12136 };
12137
12138 // model -> value
12139 var ctrl = this;
12140
12141 $scope.$watch(function ngModelWatch() {
12142 var value = ngModelGet($scope);
12143
12144 // if scope model value and ngModel value are out of sync
12145 if (ctrl.$modelValue !== value) {
12146
12147 var formatters = ctrl.$formatters,
12148 idx = formatters.length;
12149
12150 ctrl.$modelValue = value;
12151 while(idx--) {
12152 value = formatters[idx](value);
12153 }
12154
12155 if (ctrl.$viewValue !== value) {
12156 ctrl.$viewValue = value;
12157 ctrl.$render();
12158 }
12159 }
12160 });
12161 }];
12162
12163
12164 /**
12165 * @ngdoc directive
12166 * @name ng.directive:ngModel
12167 *
12168 * @element input
12169 *
12170 * @description
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.
12173 *
12174 * `ngModel` is responsible for:
12175 *
12176 * - binding the view into the model, which other directives such as `input`, `textarea` or `select`
12177 * require,
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}.
12182 *
12183 * For basic examples, how to use `ngModel`, see:
12184 *
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}
12194 *
12195 */
12196 var ngModelDirective = function() {
12197 return {
12198 require: ['ngModel', '^?form'],
12199 controller: NgModelController,
12200 link: function(scope, element, attr, ctrls) {
12201 // notify others, especially parent forms
12202
12203 var modelCtrl = ctrls[0],
12204 formCtrl = ctrls[1] || nullFormCtrl;
12205
12206 formCtrl.$addControl(modelCtrl);
12207
12208 element.bind('$destroy', function() {
12209 formCtrl.$removeControl(modelCtrl);
12210 });
12211 }
12212 };
12213 };
12214
12215
12216 /**
12217 * @ngdoc directive
12218 * @name ng.directive:ngChange
12219 * @restrict E
12220 *
12221 * @description
12222 * Evaluate given expression when user changes the input.
12223 * The expression is not evaluated when the value change is coming from the model.
12224 *
12225 * Note, this directive requires `ngModel` to be present.
12226 *
12227 * @element input
12228 *
12229 * @example
12230 * <doc:example>
12231 * <doc:source>
12232 * <script>
12233 * function Controller($scope) {
12234 * $scope.counter = 0;
12235 * $scope.change = function() {
12236 * $scope.counter++;
12237 * };
12238 * }
12239 * </script>
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}}
12246 * </div>
12247 * </doc:source>
12248 * <doc:scenario>
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');
12254 * });
12255 *
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');
12260 * });
12261 * </doc:scenario>
12262 * </doc:example>
12263 */
12264 var ngChangeDirective = valueFn({
12265 require: 'ngModel',
12266 link: function(scope, element, attr, ctrl) {
12267 ctrl.$viewChangeListeners.push(function() {
12268 scope.$eval(attr.ngChange);
12269 });
12270 }
12271 });
12272
12273
12274 var requiredDirective = function() {
12275 return {
12276 require: '?ngModel',
12277 link: function(scope, elm, attr, ctrl) {
12278 if (!ctrl) return;
12279 attr.required = true; // force truthy in case we are on non input element
12280
12281 var validator = function(value) {
12282 if (attr.required && (isEmpty(value) || value === false)) {
12283 ctrl.$setValidity('required', false);
12284 return;
12285 } else {
12286 ctrl.$setValidity('required', true);
12287 return value;
12288 }
12289 };
12290
12291 ctrl.$formatters.push(validator);
12292 ctrl.$parsers.unshift(validator);
12293
12294 attr.$observe('required', function() {
12295 validator(ctrl.$viewValue);
12296 });
12297 }
12298 };
12299 };
12300
12301
12302 /**
12303 * @ngdoc directive
12304 * @name ng.directive:ngList
12305 *
12306 * @description
12307 * Text input that converts between comma-separated string into an array of strings.
12308 *
12309 * @element input
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.
12312 *
12313 * @example
12314 <doc:example>
12315 <doc:source>
12316 <script>
12317 function Ctrl($scope) {
12318 $scope.names = ['igor', 'misko', 'vojta'];
12319 }
12320 </script>
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">
12324 Required!</span>
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/>
12330 </form>
12331 </doc:source>
12332 <doc:scenario>
12333 it('should initialize to model', function() {
12334 expect(binding('names')).toEqual('["igor","misko","vojta"]');
12335 expect(binding('myForm.namesInput.$valid')).toEqual('true');
12336 });
12337
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');
12342 });
12343 </doc:scenario>
12344 </doc:example>
12345 */
12346 var ngListDirective = function() {
12347 return {
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 || ',';
12352
12353 var parse = function(viewValue) {
12354 var list = [];
12355
12356 if (viewValue) {
12357 forEach(viewValue.split(separator), function(value) {
12358 if (value) list.push(trim(value));
12359 });
12360 }
12361
12362 return list;
12363 };
12364
12365 ctrl.$parsers.push(parse);
12366 ctrl.$formatters.push(function(value) {
12367 if (isArray(value)) {
12368 return value.join(', ');
12369 }
12370
12371 return undefined;
12372 });
12373 }
12374 };
12375 };
12376
12377
12378 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
12379
12380 var ngValueDirective = function() {
12381 return {
12382 priority: 100,
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));
12387 };
12388 } else {
12389 return function(scope, elm, attr) {
12390 scope.$watch(attr.ngValue, function valueWatchAction(value) {
12391 attr.$set('value', value, false);
12392 });
12393 };
12394 }
12395 }
12396 };
12397 };
12398
12399 /**
12400 * @ngdoc directive
12401 * @name ng.directive:ngBind
12402 *
12403 * @description
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.
12407 *
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.
12410 *
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.
12415 *
12416 * An alternative solution to this problem would be using the
12417 * {@link ng.directive:ngCloak ngCloak} directive.
12418 *
12419 *
12420 * @element ANY
12421 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
12422 *
12423 * @example
12424 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
12425 <doc:example>
12426 <doc:source>
12427 <script>
12428 function Ctrl($scope) {
12429 $scope.name = 'Whirled';
12430 }
12431 </script>
12432 <div ng-controller="Ctrl">
12433 Enter name: <input type="text" ng-model="name"><br>
12434 Hello <span ng-bind="name"></span>!
12435 </div>
12436 </doc:source>
12437 <doc:scenario>
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');
12442 });
12443 </doc:scenario>
12444 </doc:example>
12445 */
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);
12450 });
12451 });
12452
12453
12454 /**
12455 * @ngdoc directive
12456 * @name ng.directive:ngBindTemplate
12457 *
12458 * @description
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.)
12464 *
12465 * @element ANY
12466 * @param {string} ngBindTemplate template of form
12467 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
12468 *
12469 * @example
12470 * Try it here: enter text in text box and watch the greeting change.
12471 <doc:example>
12472 <doc:source>
12473 <script>
12474 function Ctrl($scope) {
12475 $scope.salutation = 'Hello';
12476 $scope.name = 'World';
12477 }
12478 </script>
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>
12483 </div>
12484 </doc:source>
12485 <doc:scenario>
12486 it('should check ng-bind', function() {
12487 expect(using('.doc-example-live').binding('salutation')).
12488 toBe('Hello');
12489 expect(using('.doc-example-live').binding('name')).
12490 toBe('World');
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')).
12494 toBe('Greetings');
12495 expect(using('.doc-example-live').binding('name')).
12496 toBe('user');
12497 });
12498 </doc:scenario>
12499 </doc:example>
12500 */
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);
12508 });
12509 }
12510 }];
12511
12512
12513 /**
12514 * @ngdoc directive
12515 * @name ng.directive:ngBindHtmlUnsafe
12516 *
12517 * @description
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.
12522 *
12523 * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
12524 *
12525 * @element ANY
12526 * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate.
12527 */
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 || '');
12533 });
12534 };
12535 }];
12536
12537 function classDirective(name, selector) {
12538 name = 'ngClass' + name;
12539 return ngDirective(function(scope, element, attr) {
12540 var oldVal = undefined;
12541
12542 scope.$watch(attr[name], ngClassWatchAction, true);
12543
12544 attr.$observe('class', function(value) {
12545 var ngClass = scope.$eval(attr[name]);
12546 ngClassWatchAction(ngClass, ngClass);
12547 });
12548
12549
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]));
12556 } else {
12557 removeClass(scope.$eval(attr[name]));
12558 }
12559 }
12560 });
12561 }
12562
12563
12564 function ngClassWatchAction(newVal) {
12565 if (selector === true || scope.$index % 2 === selector) {
12566 if (oldVal && !equals(newVal,oldVal)) {
12567 removeClass(oldVal);
12568 }
12569 addClass(newVal);
12570 }
12571 oldVal = copy(newVal);
12572 }
12573
12574
12575 function removeClass(classVal) {
12576 if (isObject(classVal) && !isArray(classVal)) {
12577 classVal = map(classVal, function(v, k) { if (v) return k });
12578 }
12579 element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal);
12580 }
12581
12582
12583 function addClass(classVal) {
12584 if (isObject(classVal) && !isArray(classVal)) {
12585 classVal = map(classVal, function(v, k) { if (v) return k });
12586 }
12587 if (classVal) {
12588 element.addClass(isArray(classVal) ? classVal.join(' ') : classVal);
12589 }
12590 }
12591 });
12592 }
12593
12594 /**
12595 * @ngdoc directive
12596 * @name ng.directive:ngClass
12597 *
12598 * @description
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.
12601 *
12602 * The directive won't add duplicate classes if a particular class was already set.
12603 *
12604 * When the expression changes, the previously added classes are removed and only then the
12605 * new classes are added.
12606 *
12607 * @element ANY
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.
12611 *
12612 * @example
12613 <example>
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=''">
12617 <br>
12618 <span ng-class="myVar">Sample Text</span>
12619 </file>
12620 <file name="style.css">
12621 .my-class {
12622 color: red;
12623 }
12624 </file>
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/);
12629
12630 using('.doc-example-live').element(':button:first').click();
12631
12632 expect(element('.doc-example-live span').prop('className')).
12633 toMatch(/my-class/);
12634
12635 using('.doc-example-live').element(':button:last').click();
12636
12637 expect(element('.doc-example-live span').prop('className')).not().
12638 toMatch(/my-class/);
12639 });
12640 </file>
12641 </example>
12642 */
12643 var ngClassDirective = classDirective('', true);
12644
12645 /**
12646 * @ngdoc directive
12647 * @name ng.directive:ngClassOdd
12648 *
12649 * @description
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.
12653 *
12654 * This directive can be applied only within a scope of an
12655 * {@link ng.directive:ngRepeat ngRepeat}.
12656 *
12657 * @element ANY
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.
12660 *
12661 * @example
12662 <example>
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'">
12667 {{name}}
12668 </span>
12669 </li>
12670 </ol>
12671 </file>
12672 <file name="style.css">
12673 .odd {
12674 color: red;
12675 }
12676 .even {
12677 color: blue;
12678 }
12679 </file>
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')).
12683 toMatch(/odd/);
12684 expect(element('.doc-example-live li:last span').prop('className')).
12685 toMatch(/even/);
12686 });
12687 </file>
12688 </example>
12689 */
12690 var ngClassOddDirective = classDirective('Odd', 0);
12691
12692 /**
12693 * @ngdoc directive
12694 * @name ng.directive:ngClassEven
12695 *
12696 * @description
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.
12700 *
12701 * This directive can be applied only within a scope of an
12702 * {@link ng.directive:ngRepeat ngRepeat}.
12703 *
12704 * @element ANY
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.
12707 *
12708 * @example
12709 <example>
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}} &nbsp; &nbsp; &nbsp;
12715 </span>
12716 </li>
12717 </ol>
12718 </file>
12719 <file name="style.css">
12720 .odd {
12721 color: red;
12722 }
12723 .even {
12724 color: blue;
12725 }
12726 </file>
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')).
12730 toMatch(/odd/);
12731 expect(element('.doc-example-live li:last span').prop('className')).
12732 toMatch(/even/);
12733 });
12734 </file>
12735 </example>
12736 */
12737 var ngClassEvenDirective = classDirective('Even', 1);
12738
12739 /**
12740 * @ngdoc directive
12741 * @name ng.directive:ngCloak
12742 *
12743 * @description
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.
12747 *
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.
12750 *
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:
12753 *
12754 * <pre>
12755 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
12756 * display: none;
12757 * }
12758 * </pre>
12759 *
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.
12764 *
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
12767 * application.
12768 *
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.
12772 *
12773 * @element ANY
12774 *
12775 * @example
12776 <doc:example>
12777 <doc:source>
12778 <div id="template1" ng-cloak>{{ 'hello' }}</div>
12779 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
12780 </doc:source>
12781 <doc:scenario>
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();
12787 });
12788 </doc:scenario>
12789 </doc:example>
12790 *
12791 */
12792 var ngCloakDirective = ngDirective({
12793 compile: function(element, attr) {
12794 attr.$set('ngCloak', undefined);
12795 element.removeClass('ng-cloak');
12796 }
12797 });
12798
12799 /**
12800 * @ngdoc directive
12801 * @name ng.directive:ngController
12802 *
12803 * @description
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.
12806 *
12807 * MVC components in angular:
12808 *
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.
12813 *
12814 * Note that an alternative way to define controllers is via the {@link ng.$route $route} service.
12815 *
12816 * @element ANY
12817 * @scope
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.
12821 *
12822 * @example
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.
12829 <doc:example>
12830 <doc:source>
12831 <script>
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'} ];
12837
12838 $scope.greet = function() {
12839 alert(this.name);
12840 };
12841
12842 $scope.addContact = function() {
12843 this.contacts.push({type:'email', value:'yourname@example.org'});
12844 };
12845
12846 $scope.removeContact = function(contactToRemove) {
12847 var index = this.contacts.indexOf(contactToRemove);
12848 this.contacts.splice(index, 1);
12849 };
12850
12851 $scope.clearContact = function(contact) {
12852 contact.type = 'phone';
12853 contact.value = '';
12854 };
12855 }
12856 </script>
12857 <div ng-controller="SettingsController">
12858 Name: <input type="text" ng-model="name"/>
12859 [ <a href="" ng-click="greet()">greet</a> ]<br/>
12860 Contact:
12861 <ul>
12862 <li ng-repeat="contact in contacts">
12863 <select ng-model="contact.type">
12864 <option>phone</option>
12865 <option>email</option>
12866 </select>
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> ]
12870 </li>
12871 <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
12872 </ul>
12873 </div>
12874 </doc:source>
12875 <doc:scenario>
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');
12882
12883 element('.doc-example-live li:first a:contains("clear")').click();
12884 expect(element('.doc-example-live li:first input').val()).toBe('');
12885
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');
12889 });
12890 </doc:scenario>
12891 </doc:example>
12892 */
12893 var ngControllerDirective = [function() {
12894 return {
12895 scope: true,
12896 controller: '@'
12897 };
12898 }];
12899
12900 /**
12901 * @ngdoc directive
12902 * @name ng.directive:ngCsp
12903 * @priority 1000
12904 *
12905 * @element html
12906 * @description
12907 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
12908 *
12909 * This is necessary when developing things like Google Chrome Extensions.
12910 *
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.
12914 *
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
12918 * be raised.
12919 *
12920 * In order to use this feature put `ngCsp` directive on the root element of the application.
12921 *
12922 * @example
12923 * This example shows how to apply the `ngCsp` directive to the `html` tag.
12924 <pre>
12925 <!doctype html>
12926 <html ng-app ng-csp>
12927 ...
12928 ...
12929 </html>
12930 </pre>
12931 */
12932
12933 var ngCspDirective = ['$sniffer', function($sniffer) {
12934 return {
12935 priority: 1000,
12936 compile: function() {
12937 $sniffer.csp = true;
12938 }
12939 };
12940 }];
12941
12942 /**
12943 * @ngdoc directive
12944 * @name ng.directive:ngClick
12945 *
12946 * @description
12947 * The ngClick allows you to specify custom behavior when
12948 * element is clicked.
12949 *
12950 * @element ANY
12951 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
12952 * click. (Event object is available as `$event`)
12953 *
12954 * @example
12955 <doc:example>
12956 <doc:source>
12957 <button ng-click="count = count + 1" ng-init="count=0">
12958 Increment
12959 </button>
12960 count: {{count}}
12961 </doc:source>
12962 <doc:scenario>
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');
12967 });
12968 </doc:scenario>
12969 </doc:example>
12970 */
12971 /*
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.
12974 *
12975 * Events that are handled via these handler are always configured not to propagate further.
12976 */
12977 var ngEventDirectives = {};
12978 forEach(
12979 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
12980 function(name) {
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});
12988 });
12989 });
12990 };
12991 }];
12992 }
12993 );
12994
12995 /**
12996 * @ngdoc directive
12997 * @name ng.directive:ngDblclick
12998 *
12999 * @description
13000 * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
13001 *
13002 * @element ANY
13003 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
13004 * dblclick. (Event object is available as `$event`)
13005 *
13006 * @example
13007 * See {@link ng.directive:ngClick ngClick}
13008 */
13009
13010
13011 /**
13012 * @ngdoc directive
13013 * @name ng.directive:ngMousedown
13014 *
13015 * @description
13016 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
13017 *
13018 * @element ANY
13019 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
13020 * mousedown. (Event object is available as `$event`)
13021 *
13022 * @example
13023 * See {@link ng.directive:ngClick ngClick}
13024 */
13025
13026
13027 /**
13028 * @ngdoc directive
13029 * @name ng.directive:ngMouseup
13030 *
13031 * @description
13032 * Specify custom behavior on mouseup event.
13033 *
13034 * @element ANY
13035 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
13036 * mouseup. (Event object is available as `$event`)
13037 *
13038 * @example
13039 * See {@link ng.directive:ngClick ngClick}
13040 */
13041
13042 /**
13043 * @ngdoc directive
13044 * @name ng.directive:ngMouseover
13045 *
13046 * @description
13047 * Specify custom behavior on mouseover event.
13048 *
13049 * @element ANY
13050 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
13051 * mouseover. (Event object is available as `$event`)
13052 *
13053 * @example
13054 * See {@link ng.directive:ngClick ngClick}
13055 */
13056
13057
13058 /**
13059 * @ngdoc directive
13060 * @name ng.directive:ngMouseenter
13061 *
13062 * @description
13063 * Specify custom behavior on mouseenter event.
13064 *
13065 * @element ANY
13066 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
13067 * mouseenter. (Event object is available as `$event`)
13068 *
13069 * @example
13070 * See {@link ng.directive:ngClick ngClick}
13071 */
13072
13073
13074 /**
13075 * @ngdoc directive
13076 * @name ng.directive:ngMouseleave
13077 *
13078 * @description
13079 * Specify custom behavior on mouseleave event.
13080 *
13081 * @element ANY
13082 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
13083 * mouseleave. (Event object is available as `$event`)
13084 *
13085 * @example
13086 * See {@link ng.directive:ngClick ngClick}
13087 */
13088
13089
13090 /**
13091 * @ngdoc directive
13092 * @name ng.directive:ngMousemove
13093 *
13094 * @description
13095 * Specify custom behavior on mousemove event.
13096 *
13097 * @element ANY
13098 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
13099 * mousemove. (Event object is available as `$event`)
13100 *
13101 * @example
13102 * See {@link ng.directive:ngClick ngClick}
13103 */
13104
13105
13106 /**
13107 * @ngdoc directive
13108 * @name ng.directive:ngSubmit
13109 *
13110 * @description
13111 * Enables binding angular expressions to onsubmit events.
13112 *
13113 * Additionally it prevents the default action (which for form means sending the request to the
13114 * server and reloading the current page).
13115 *
13116 * @element form
13117 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
13118 *
13119 * @example
13120 <doc:example>
13121 <doc:source>
13122 <script>
13123 function Ctrl($scope) {
13124 $scope.list = [];
13125 $scope.text = 'hello';
13126 $scope.submit = function() {
13127 if (this.text) {
13128 this.list.push(this.text);
13129 this.text = '';
13130 }
13131 };
13132 }
13133 </script>
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>
13139 </form>
13140 </doc:source>
13141 <doc:scenario>
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('');
13147 });
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"]');
13153 });
13154 </doc:scenario>
13155 </doc:example>
13156 */
13157 var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
13158 element.bind('submit', function() {
13159 scope.$apply(attrs.ngSubmit);
13160 });
13161 });
13162
13163 /**
13164 * @ngdoc directive
13165 * @name ng.directive:ngInclude
13166 * @restrict ECA
13167 *
13168 * @description
13169 * Fetches, compiles and includes an external HTML fragment.
13170 *
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).
13174 *
13175 * @scope
13176 *
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.
13180 *
13181 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
13182 * $anchorScroll} to scroll the viewport after the content is loaded.
13183 *
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.
13187 *
13188 * @example
13189 <example>
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>
13194 </select>
13195 url of the template: <tt>{{template.url}}</tt>
13196 <hr/>
13197 <div ng-include src="template.url"></div>
13198 </div>
13199 </file>
13200 <file name="script.js">
13201 function Ctrl($scope) {
13202 $scope.templates =
13203 [ { name: 'template1.html', url: 'template1.html'}
13204 , { name: 'template2.html', url: 'template2.html'} ];
13205 $scope.template = $scope.templates[0];
13206 }
13207 </file>
13208 <file name="template1.html">
13209 Content of template1.html
13210 </file>
13211 <file name="template2.html">
13212 Content of template2.html
13213 </file>
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/);
13218 });
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/);
13223 });
13224 it('should change to blank', function() {
13225 select('template').option('');
13226 expect(element('.doc-example-live [ng-include]').text()).toEqual('');
13227 });
13228 </file>
13229 </example>
13230 */
13231
13232
13233 /**
13234 * @ngdoc event
13235 * @name ng.directive:ngInclude#$includeContentLoaded
13236 * @eventOf ng.directive:ngInclude
13237 * @eventType emit on the current ngInclude scope
13238 * @description
13239 * Emitted every time the ngInclude content is reloaded.
13240 */
13241 var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
13242 function($http, $templateCache, $anchorScroll, $compile) {
13243 return {
13244 restrict: 'ECA',
13245 terminal: true,
13246 compile: function(element, attr) {
13247 var srcExp = attr.ngInclude || attr.src,
13248 onloadExp = attr.onload || '',
13249 autoScrollExp = attr.autoscroll;
13250
13251 return function(scope, element) {
13252 var changeCounter = 0,
13253 childScope;
13254
13255 var clearContent = function() {
13256 if (childScope) {
13257 childScope.$destroy();
13258 childScope = null;
13259 }
13260
13261 element.html('');
13262 };
13263
13264 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
13265 var thisChangeId = ++changeCounter;
13266
13267 if (src) {
13268 $http.get(src, {cache: $templateCache}).success(function(response) {
13269 if (thisChangeId !== changeCounter) return;
13270
13271 if (childScope) childScope.$destroy();
13272 childScope = scope.$new();
13273
13274 element.html(response);
13275 $compile(element.contents())(childScope);
13276
13277 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
13278 $anchorScroll();
13279 }
13280
13281 childScope.$emit('$includeContentLoaded');
13282 scope.$eval(onloadExp);
13283 }).error(function() {
13284 if (thisChangeId === changeCounter) clearContent();
13285 });
13286 } else clearContent();
13287 });
13288 };
13289 }
13290 };
13291 }];
13292
13293 /**
13294 * @ngdoc directive
13295 * @name ng.directive:ngInit
13296 *
13297 * @description
13298 * The `ngInit` directive specifies initialization tasks to be executed
13299 * before the template enters execution mode during bootstrap.
13300 *
13301 * @element ANY
13302 * @param {expression} ngInit {@link guide/expression Expression} to eval.
13303 *
13304 * @example
13305 <doc:example>
13306 <doc:source>
13307 <div ng-init="greeting='Hello'; person='World'">
13308 {{greeting}} {{person}}!
13309 </div>
13310 </doc:source>
13311 <doc:scenario>
13312 it('should check greeting', function() {
13313 expect(binding('greeting')).toBe('Hello');
13314 expect(binding('person')).toBe('World');
13315 });
13316 </doc:scenario>
13317 </doc:example>
13318 */
13319 var ngInitDirective = ngDirective({
13320 compile: function() {
13321 return {
13322 pre: function(scope, element, attrs) {
13323 scope.$eval(attrs.ngInit);
13324 }
13325 }
13326 }
13327 });
13328
13329 /**
13330 * @ngdoc directive
13331 * @name ng.directive:ngNonBindable
13332 * @priority 1000
13333 *
13334 * @description
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.
13337 *
13338 * @element ANY
13339 *
13340 * @example
13341 * In this example there are two location where a simple binding (`{{}}`) is present, but the one
13342 * wrapped in `ngNonBindable` is left alone.
13343 *
13344 * @example
13345 <doc:example>
13346 <doc:source>
13347 <div>Normal: {{1 + 2}}</div>
13348 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
13349 </doc:source>
13350 <doc:scenario>
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()).
13354 toMatch(/1 \+ 2/);
13355 });
13356 </doc:scenario>
13357 </doc:example>
13358 */
13359 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
13360
13361 /**
13362 * @ngdoc directive
13363 * @name ng.directive:ngPluralize
13364 * @restrict EA
13365 *
13366 * @description
13367 * # Overview
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.
13374 *
13375 * # Plural categories and explicit number rules
13376 * There are two
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".
13379 *
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.
13384 *
13385 * # Configuring ngPluralize
13386 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
13387 * You can also provide an optional attribute, `offset`.
13388 *
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.
13391 *
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.
13395 *
13396 * The following example shows how to configure ngPluralize:
13397 *
13398 * <pre>
13399 * <ng-pluralize count="personCount"
13400 when="{'0': 'Nobody is viewing.',
13401 * 'one': '1 person is viewing.',
13402 * 'other': '{} people are viewing.'}">
13403 * </ng-pluralize>
13404 *</pre>
13405 *
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".
13411 *
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>.
13416 *
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:
13423 *
13424 * <pre>
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.'}">
13431 * </ng-pluralize>
13432 * </pre>
13433 *
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"
13440 * is shown.
13441 *
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".
13446 *
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.
13450 *
13451 * @example
13452 <doc:example>
13453 <doc:source>
13454 <script>
13455 function Ctrl($scope) {
13456 $scope.person1 = 'Igor';
13457 $scope.person2 = 'Misko';
13458 $scope.personCount = 1;
13459 }
13460 </script>
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/>
13465
13466 <!--- Example with simple pluralization rules for en locale --->
13467 Without Offset:
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>
13473
13474 <!--- Example with offset --->
13475 With Offset(2):
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.'}">
13482 </ng-pluralize>
13483 </div>
13484 </doc:source>
13485 <doc:scenario>
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.');
13491
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.');
13497
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.');
13503
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.');
13509
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.');
13515 });
13516
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.');
13521
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.');
13526 });
13527 </doc:scenario>
13528 </doc:example>
13529 */
13530 var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
13531 var BRACE = /{}/g;
13532 return {
13533 restrict: 'EA',
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),
13539 whensExpFns = {},
13540 startSymbol = $interpolate.startSymbol(),
13541 endSymbol = $interpolate.endSymbol();
13542
13543 forEach(whens, function(expression, key) {
13544 whensExpFns[key] =
13545 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
13546 offset + endSymbol));
13547 });
13548
13549 scope.$watch(function ngPluralizeWatch() {
13550 var value = parseFloat(scope.$eval(numberExp));
13551
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);
13557 } else {
13558 return '';
13559 }
13560 }, function ngPluralizeWatchAction(newVal) {
13561 element.text(newVal);
13562 });
13563 }
13564 };
13565 }];
13566
13567 /**
13568 * @ngdoc directive
13569 * @name ng.directive:ngRepeat
13570 *
13571 * @description
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.
13575 *
13576 * Special properties are exposed on the local scope of each template instance, including:
13577 *
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.
13582 *
13583 *
13584 * @element ANY
13585 * @scope
13586 * @priority 1000
13587 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
13588 * formats are currently supported:
13589 *
13590 * * `variable in expression` – where variable is the user defined loop variable and `expression`
13591 * is a scope expression giving the collection to enumerate.
13592 *
13593 * For example: `track in cd.tracks`.
13594 *
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.
13597 *
13598 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
13599 *
13600 * @example
13601 * This example initializes the scope to a list of names and
13602 * then uses `ngRepeat` to display every person:
13603 <doc:example>
13604 <doc:source>
13605 <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
13606 I have {{friends.length}} friends. They are:
13607 <ul>
13608 <li ng-repeat="friend in friends">
13609 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
13610 </li>
13611 </ul>
13612 </div>
13613 </doc:source>
13614 <doc:scenario>
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"]);
13620 });
13621 </doc:scenario>
13622 </doc:example>
13623 */
13624 var ngRepeatDirective = ngDirective({
13625 transclude: 'element',
13626 priority: 1000,
13627 terminal: true,
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;
13633 if (! match) {
13634 throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
13635 expression + "'.");
13636 }
13637 lhs = match[1];
13638 rhs = match[2];
13639 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
13640 if (!match) {
13641 throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
13642 lhs + "'.");
13643 }
13644 valueIdent = match[3] || match[1];
13645 keyIdent = match[2];
13646
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();
13655
13656 scope.$watch(function ngRepeatWatch(scope){
13657 var index, length,
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(),
13663 arrayBound,
13664 childScope,
13665 key, value, // key/value of iteration
13666 array,
13667 last; // last object information {scope, element, index}
13668
13669
13670
13671 if (!isArray(collection)) {
13672 // if object, extract keys, sort them and use to determine order of iteration over obj props
13673 array = [];
13674 for(key in collection) {
13675 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
13676 array.push(key);
13677 }
13678 }
13679 array.sort();
13680 } else {
13681 array = collection || [];
13682 }
13683
13684 arrayBound = array.length-1;
13685
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];
13690
13691 last = lastOrder.shift(value);
13692
13693 if (last) {
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);
13698
13699 if (index === last.index) {
13700 // do nothing
13701 cursor = last.element;
13702 } else {
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;
13710 }
13711 } else {
13712 // new item which we don't know about
13713 childScope = scope.$new();
13714 }
13715
13716 childScope[valueIdent] = value;
13717 if (keyIdent) childScope[keyIdent] = key;
13718 childScope.$index = index;
13719
13720 childScope.$first = (index === 0);
13721 childScope.$last = (index === arrayBound);
13722 childScope.$middle = !(childScope.$first || childScope.$last);
13723
13724 if (!last) {
13725 linker(childScope, function(clone){
13726 cursor.after(clone);
13727 last = {
13728 scope: childScope,
13729 element: (cursor = clone),
13730 index: index
13731 };
13732 nextOrder.push(value, last);
13733 });
13734 }
13735 }
13736
13737 //shrink children
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();
13745 }
13746 }
13747 }
13748
13749 lastOrder = nextOrder;
13750 });
13751 };
13752 }
13753 });
13754
13755 /**
13756 * @ngdoc directive
13757 * @name ng.directive:ngShow
13758 *
13759 * @description
13760 * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
13761 * conditionally.
13762 *
13763 * @element ANY
13764 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
13765 * then the element is shown or hidden respectively.
13766 *
13767 * @example
13768 <doc:example>
13769 <doc:source>
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>
13773 </doc:source>
13774 <doc:scenario>
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);
13778
13779 input('checked').check();
13780
13781 expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
13782 expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
13783 });
13784 </doc:scenario>
13785 </doc:example>
13786 */
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');
13791 });
13792 });
13793
13794
13795 /**
13796 * @ngdoc directive
13797 * @name ng.directive:ngHide
13798 *
13799 * @description
13800 * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
13801 * conditionally.
13802 *
13803 * @element ANY
13804 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
13805 * the element is shown or hidden respectively.
13806 *
13807 * @example
13808 <doc:example>
13809 <doc:source>
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>
13813 </doc:source>
13814 <doc:scenario>
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);
13818
13819 input('checked').check();
13820
13821 expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
13822 expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
13823 });
13824 </doc:scenario>
13825 </doc:example>
13826 */
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' : '');
13831 });
13832 });
13833
13834 /**
13835 * @ngdoc directive
13836 * @name ng.directive:ngStyle
13837 *
13838 * @description
13839 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
13840 *
13841 * @element ANY
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
13844 * keys.
13845 *
13846 * @example
13847 <example>
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={}">
13851 <br/>
13852 <span ng-style="myStyle">Sample Text</span>
13853 <pre>myStyle={{myStyle}}</pre>
13854 </file>
13855 <file name="style.css">
13856 span {
13857 color: black;
13858 }
13859 </file>
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)');
13867 });
13868 </file>
13869 </example>
13870 */
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, '');});
13875 }
13876 if (newStyles) element.css(newStyles);
13877 }, true);
13878 });
13879
13880 /**
13881 * @ngdoc directive
13882 * @name ng.directive:ngSwitch
13883 * @restrict EA
13884 *
13885 * @description
13886 * Conditionally change the DOM structure.
13887 *
13888 * @usage
13889 * <ANY ng-switch="expression">
13890 * <ANY ng-switch-when="matchValue1">...</ANY>
13891 * <ANY ng-switch-when="matchValue2">...</ANY>
13892 * ...
13893 * <ANY ng-switch-default>...</ANY>
13894 * </ANY>
13895 *
13896 * @scope
13897 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
13898 * @paramDescription
13899 * On child elments add:
13900 *
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.
13904 *
13905 * @example
13906 <doc:example>
13907 <doc:source>
13908 <script>
13909 function Ctrl($scope) {
13910 $scope.items = ['settings', 'home', 'other'];
13911 $scope.selection = $scope.items[0];
13912 }
13913 </script>
13914 <div ng-controller="Ctrl">
13915 <select ng-model="selection" ng-options="item for item in items">
13916 </select>
13917 <tt>selection={{selection}}</tt>
13918 <hr/>
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>
13923 </div>
13924 </div>
13925 </doc:source>
13926 <doc:scenario>
13927 it('should start in settings', function() {
13928 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
13929 });
13930 it('should change to home', function() {
13931 select('selection').option('home');
13932 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
13933 });
13934 it('should select deafault', function() {
13935 select('selection').option('other');
13936 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
13937 });
13938 </doc:scenario>
13939 </doc:example>
13940 */
13941 var NG_SWITCH = 'ng-switch';
13942 var ngSwitchDirective = valueFn({
13943 restrict: 'EA',
13944 require: 'ngSwitch',
13945 // asks for $scope to fool the BC controller module
13946 controller: ['$scope', function ngSwitchController() {
13947 this.cases = {};
13948 }],
13949 link: function(scope, element, attr, ctrl) {
13950 var watchExpr = attr.ngSwitch || attr.on,
13951 selectedTransclude,
13952 selectedElement,
13953 selectedScope;
13954
13955 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
13956 if (selectedElement) {
13957 selectedScope.$destroy();
13958 selectedElement.remove();
13959 selectedElement = selectedScope = null;
13960 }
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);
13967 });
13968 }
13969 });
13970 }
13971 });
13972
13973 var ngSwitchWhenDirective = ngDirective({
13974 transclude: 'element',
13975 priority: 500,
13976 require: '^ngSwitch',
13977 compile: function(element, attrs, transclude) {
13978 return function(scope, element, attr, ctrl) {
13979 ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
13980 };
13981 }
13982 });
13983
13984 var ngSwitchDefaultDirective = ngDirective({
13985 transclude: 'element',
13986 priority: 500,
13987 require: '^ngSwitch',
13988 compile: function(element, attrs, transclude) {
13989 return function(scope, element, attr, ctrl) {
13990 ctrl.cases['?'] = transclude;
13991 };
13992 }
13993 });
13994
13995 /**
13996 * @ngdoc directive
13997 * @name ng.directive:ngTransclude
13998 *
13999 * @description
14000 * Insert the transcluded DOM here.
14001 *
14002 * @element ANY
14003 *
14004 * @example
14005 <doc:example module="transclude">
14006 <doc:source>
14007 <script>
14008 function Ctrl($scope) {
14009 $scope.title = 'Lorem Ipsum';
14010 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
14011 }
14012
14013 angular.module('transclude', [])
14014 .directive('pane', function(){
14015 return {
14016 restrict: 'E',
14017 transclude: true,
14018 scope: 'isolate',
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>' +
14023 '</div>'
14024 };
14025 });
14026 </script>
14027 <div ng-controller="Ctrl">
14028 <input ng-model="title"><br>
14029 <textarea ng-model="text"></textarea> <br/>
14030 <pane title="{{title}}">{{text}}</pane>
14031 </div>
14032 </doc:source>
14033 <doc:scenario>
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');
14039 });
14040 </doc:scenario>
14041 </doc:example>
14042 *
14043 */
14044 var ngTranscludeDirective = ngDirective({
14045 controller: ['$transclude', '$element', function($transclude, $element) {
14046 $transclude(function(clone) {
14047 $element.append(clone);
14048 });
14049 }]
14050 });
14051
14052 /**
14053 * @ngdoc directive
14054 * @name ng.directive:ngView
14055 * @restrict ECA
14056 *
14057 * @description
14058 * # Overview
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.
14063 *
14064 * @scope
14065 * @example
14066 <example module="ngView">
14067 <file name="index.html">
14068 <div ng-controller="MainCntl">
14069 Choose:
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/>
14075
14076 <div ng-view></div>
14077 <hr />
14078
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>
14084 </div>
14085 </file>
14086
14087 <file name="book.html">
14088 controller: {{name}}<br />
14089 Book Id: {{params.bookId}}<br />
14090 </file>
14091
14092 <file name="chapter.html">
14093 controller: {{name}}<br />
14094 Book Id: {{params.bookId}}<br />
14095 Chapter Id: {{params.chapterId}}
14096 </file>
14097
14098 <file name="script.js">
14099 angular.module('ngView', [], function($routeProvider, $locationProvider) {
14100 $routeProvider.when('/Book/:bookId', {
14101 templateUrl: 'book.html',
14102 controller: BookCntl
14103 });
14104 $routeProvider.when('/Book/:bookId/ch/:chapterId', {
14105 templateUrl: 'chapter.html',
14106 controller: ChapterCntl
14107 });
14108
14109 // configure html5 to get links working on jsfiddle
14110 $locationProvider.html5Mode(true);
14111 });
14112
14113 function MainCntl($scope, $route, $routeParams, $location) {
14114 $scope.$route = $route;
14115 $scope.$location = $location;
14116 $scope.$routeParams = $routeParams;
14117 }
14118
14119 function BookCntl($scope, $routeParams) {
14120 $scope.name = "BookCntl";
14121 $scope.params = $routeParams;
14122 }
14123
14124 function ChapterCntl($scope, $routeParams) {
14125 $scope.name = "ChapterCntl";
14126 $scope.params = $routeParams;
14127 }
14128 </file>
14129
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/);
14137
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/);
14142 });
14143 </file>
14144 </example>
14145 */
14146
14147
14148 /**
14149 * @ngdoc event
14150 * @name ng.directive:ngView#$viewContentLoaded
14151 * @eventOf ng.directive:ngView
14152 * @eventType emit on the current ngView scope
14153 * @description
14154 * Emitted every time the ngView content is reloaded.
14155 */
14156 var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
14157 '$controller',
14158 function($http, $templateCache, $route, $anchorScroll, $compile,
14159 $controller) {
14160 return {
14161 restrict: 'ECA',
14162 terminal: true,
14163 link: function(scope, element, attr) {
14164 var lastScope,
14165 onloadExp = attr.onload || '';
14166
14167 scope.$on('$routeChangeSuccess', update);
14168 update();
14169
14170
14171 function destroyLastScope() {
14172 if (lastScope) {
14173 lastScope.$destroy();
14174 lastScope = null;
14175 }
14176 }
14177
14178 function clearContent() {
14179 element.html('');
14180 destroyLastScope();
14181 }
14182
14183 function update() {
14184 var locals = $route.current && $route.current.locals,
14185 template = locals && locals.$template;
14186
14187 if (template) {
14188 element.html(template);
14189 destroyLastScope();
14190
14191 var link = $compile(element.contents()),
14192 current = $route.current,
14193 controller;
14194
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);
14200 }
14201
14202 link(lastScope);
14203 lastScope.$emit('$viewContentLoaded');
14204 lastScope.$eval(onloadExp);
14205
14206 // $anchorScroll might listen on event...
14207 $anchorScroll();
14208 } else {
14209 clearContent();
14210 }
14211 }
14212 }
14213 };
14214 }];
14215
14216 /**
14217 * @ngdoc directive
14218 * @name ng.directive:script
14219 *
14220 * @description
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.
14223 *
14224 * @restrict E
14225 * @param {'text/ng-template'} type must be set to `'text/ng-template'`
14226 *
14227 * @example
14228 <doc:example>
14229 <doc:source>
14230 <script type="text/ng-template" id="/tpl.html">
14231 Content of the template.
14232 </script>
14233
14234 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
14235 <div id="tpl-content" ng-include src="currentTpl"></div>
14236 </doc:source>
14237 <doc:scenario>
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/);
14241 });
14242 </doc:scenario>
14243 </doc:example>
14244 */
14245 var scriptDirective = ['$templateCache', function($templateCache) {
14246 return {
14247 restrict: 'E',
14248 terminal: true,
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;
14254
14255 $templateCache.put(templateUrl, text);
14256 }
14257 }
14258 };
14259 }];
14260
14261 /**
14262 * @ngdoc directive
14263 * @name ng.directive:select
14264 * @restrict E
14265 *
14266 * @description
14267 * HTML `SELECT` element with angular data-binding.
14268 *
14269 * # `ngOptions`
14270 *
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.
14274 *˝˝
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.
14278 *
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.
14282 *
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.
14287 *
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:
14295 *
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`
14307 *
14308 * Where:
14309 *
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>`
14319 * DOM element.
14320 *
14321 * @example
14322 <doc:example>
14323 <doc:source>
14324 <script>
14325 function MyCntrl($scope) {
14326 $scope.colors = [
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'}
14332 ];
14333 $scope.color = $scope.colors[2]; // red
14334 }
14335 </script>
14336 <div ng-controller="MyCntrl">
14337 <ul>
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>]
14341 </li>
14342 <li>
14343 [<a href ng-click="colors.push({})">add</a>]
14344 </li>
14345 </ul>
14346 <hr/>
14347 Color (null not allowed):
14348 <select ng-model="color" ng-options="c.name for c in colors"></select><br>
14349
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>
14354 </select>
14355 </span><br/>
14356
14357 Color grouped by shade:
14358 <select ng-model="color" ng-options="c.name group by c.shade for c in colors">
14359 </select><br/>
14360
14361
14362 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
14363 <hr/>
14364 Currently selected: {{ {selected_color:color} }}
14365 <div style="border:solid 1px black; height:20px"
14366 ng-style="{'background-color':color.name}">
14367 </div>
14368 </div>
14369 </doc:source>
14370 <doc:scenario>
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');
14377 });
14378 </doc:scenario>
14379 </doc:example>
14380 */
14381
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};
14387
14388 return {
14389 restrict: 'E',
14390 require: ['select', '?ngModel'],
14391 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
14392 var self = this,
14393 optionsMap = {},
14394 ngModelCtrl = nullModelCtrl,
14395 nullOption,
14396 unknownOption;
14397
14398
14399 self.databound = $attrs.ngModel;
14400
14401
14402 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
14403 ngModelCtrl = ngModelCtrl_;
14404 nullOption = nullOption_;
14405 unknownOption = unknownOption_;
14406 }
14407
14408
14409 self.addOption = function(value) {
14410 optionsMap[value] = true;
14411
14412 if (ngModelCtrl.$viewValue == value) {
14413 $element.val(value);
14414 if (unknownOption.parent()) unknownOption.remove();
14415 }
14416 };
14417
14418
14419 self.removeOption = function(value) {
14420 if (this.hasOption(value)) {
14421 delete optionsMap[value];
14422 if (ngModelCtrl.$viewValue == value) {
14423 this.renderUnknownOption(value);
14424 }
14425 }
14426 };
14427
14428
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
14435 }
14436
14437
14438 self.hasOption = function(value) {
14439 return optionsMap.hasOwnProperty(value);
14440 }
14441
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;
14445 });
14446 }],
14447
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;
14451
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)
14457 emptyOption,
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();
14463
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);
14468 break;
14469 }
14470 }
14471
14472 selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
14473
14474 // required validator
14475 if (multiple && (attr.required || attr.ngRequired)) {
14476 var requiredValidator = function(value) {
14477 ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
14478 return value;
14479 };
14480
14481 ngModelCtrl.$parsers.push(requiredValidator);
14482 ngModelCtrl.$formatters.unshift(requiredValidator);
14483
14484 attr.$observe('required', function() {
14485 requiredValidator(ngModelCtrl.$viewValue);
14486 });
14487 }
14488
14489 if (optionsExp) Options(scope, element, ngModelCtrl);
14490 else if (multiple) Multiple(scope, element, ngModelCtrl);
14491 else Single(scope, element, ngModelCtrl, selectCtrl);
14492
14493
14494 ////////////////////////////
14495
14496
14497
14498 function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
14499 ngModelCtrl.$render = function() {
14500 var viewValue = ngModelCtrl.$viewValue;
14501
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
14506 } else {
14507 if (isUndefined(viewValue) && emptyOption) {
14508 selectElement.val('');
14509 } else {
14510 selectCtrl.renderUnknownOption(viewValue);
14511 }
14512 }
14513 };
14514
14515 selectElement.bind('change', function() {
14516 scope.$apply(function() {
14517 if (unknownOption.parent()) unknownOption.remove();
14518 ngModelCtrl.$setViewValue(selectElement.val());
14519 });
14520 });
14521 }
14522
14523 function Multiple(scope, selectElement, ctrl) {
14524 var lastView;
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));
14529 });
14530 };
14531
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);
14537 ctrl.$render();
14538 }
14539 });
14540
14541 selectElement.bind('change', function() {
14542 scope.$apply(function() {
14543 var array = [];
14544 forEach(selectElement.find('option'), function(option) {
14545 if (option.selected) {
14546 array.push(option.value);
14547 }
14548 });
14549 ctrl.$setViewValue(array);
14550 });
14551 });
14552 }
14553
14554 function Options(scope, selectElement, ctrl) {
14555 var match;
14556
14557 if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
14558 throw Error(
14559 "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
14560 " but got '" + optionsExp + "'.");
14561 }
14562
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:''}]];
14573
14574 if (nullOption) {
14575 // compile the element since there might be bindings in it
14576 $compile(nullOption)(scope);
14577
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');
14581
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();
14585 }
14586
14587 // clear contents, we'll add what's needed based on the model
14588 selectElement.html('');
14589
14590 selectElement.bind('change', function() {
14591 scope.$apply(function() {
14592 var optionGroup,
14593 collection = valuesFn(scope) || [],
14594 locals = {},
14595 key, value, optionElement, index, groupIndex, length, groupLength;
14596
14597 if (multiple) {
14598 value = [];
14599 for (groupIndex = 0, groupLength = optionGroupsCache.length;
14600 groupIndex < groupLength;
14601 groupIndex++) {
14602 // list of options for that group. (first item has the parent)
14603 optionGroup = optionGroupsCache[groupIndex];
14604
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));
14611 }
14612 }
14613 }
14614 } else {
14615 key = selectElement.val();
14616 if (key == '?') {
14617 value = undefined;
14618 } else if (key == ''){
14619 value = null;
14620 } else {
14621 locals[valueName] = collection[key];
14622 if (keyName) locals[keyName] = key;
14623 value = valueFn(scope, locals);
14624 }
14625 }
14626 ctrl.$setViewValue(value);
14627 });
14628 });
14629
14630 ctrl.$render = render;
14631
14632 // TODO(vojta): can't we optimize this ?
14633 scope.$watch(render);
14634
14635 function render() {
14636 var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
14637 optionGroupNames = [''],
14638 optionGroupName,
14639 optionGroup,
14640 option,
14641 existingParent, existingOptions, existingOption,
14642 modelValue = ctrl.$modelValue,
14643 values = valuesFn(scope) || [],
14644 keys = keyName ? sortedKeys(values) : values,
14645 groupLength, length,
14646 groupIndex, index,
14647 locals = {},
14648 selected,
14649 selectedSet = false, // nothing is selected yet
14650 lastElement,
14651 element,
14652 label;
14653
14654 if (multiple) {
14655 selectedSet = new HashMap(modelValue);
14656 }
14657
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);
14665 }
14666 if (multiple) {
14667 selected = selectedSet.remove(valueFn(scope, locals)) != undefined;
14668 } else {
14669 selected = modelValue === valueFn(scope, locals);
14670 selectedSet = selectedSet || selected; // see if at least one item is selected
14671 }
14672 label = displayFn(scope, locals); // what will be seen by the user
14673 label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values
14674 optionGroup.push({
14675 id: keyName ? keys[index] : index, // either the index into array or key from object
14676 label: label,
14677 selected: selected // determine if we should be selected
14678 });
14679 }
14680 if (!multiple) {
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});
14687 }
14688 }
14689
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;
14693 groupIndex++) {
14694 // current option group name or '' if no group
14695 optionGroupName = optionGroupNames[groupIndex];
14696
14697 // list of options for that group. (first item has the parent)
14698 optionGroup = optionGroups[optionGroupName];
14699
14700 if (optionGroupsCache.length <= groupIndex) {
14701 // we need to grow the optionGroups
14702 existingParent = {
14703 element: optGroupTemplate.clone().attr('label', optionGroupName),
14704 label: optionGroup.label
14705 };
14706 existingOptions = [existingParent];
14707 optionGroupsCache.push(existingOptions);
14708 selectElement.append(existingParent.element);
14709 } else {
14710 existingOptions = optionGroupsCache[groupIndex];
14711 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
14712
14713 // update the OPTGROUP label if not the same.
14714 if (existingParent.label != optionGroupName) {
14715 existingParent.element.attr('label', existingParent.label = optionGroupName);
14716 }
14717 }
14718
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])) {
14723 // reuse elements
14724 lastElement = existingOption.element;
14725 if (existingOption.label !== option.label) {
14726 lastElement.text(existingOption.label = option.label);
14727 }
14728 if (existingOption.id !== option.id) {
14729 lastElement.val(existingOption.id = option.id);
14730 }
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));
14734 }
14735 } else {
14736 // grow elements
14737
14738 // if it's a null option
14739 if (option.id === '' && nullOption) {
14740 // put back the pre-compiled element
14741 element = nullOption;
14742 } else {
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())
14747 .val(option.id)
14748 .attr('selected', option.selected)
14749 .text(option.label);
14750 }
14751
14752 existingOptions.push(existingOption = {
14753 element: element,
14754 label: option.label,
14755 id: option.id,
14756 selected: option.selected
14757 });
14758 if (lastElement) {
14759 lastElement.after(element);
14760 } else {
14761 existingParent.element.append(element);
14762 }
14763 lastElement = element;
14764 }
14765 }
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();
14770 }
14771 }
14772 // remove any excessive OPTGROUPs from select
14773 while(optionGroupsCache.length > groupIndex) {
14774 optionGroupsCache.pop()[0].element.remove();
14775 }
14776 }
14777 }
14778 }
14779 }
14780 }];
14781
14782 var optionDirective = ['$interpolate', function($interpolate) {
14783 var nullSelectCtrl = {
14784 addOption: noop,
14785 removeOption: noop
14786 };
14787
14788 return {
14789 restrict: 'E',
14790 priority: 100,
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());
14796 }
14797 }
14798
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
14804
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);
14809 } else {
14810 selectCtrl = nullSelectCtrl;
14811 }
14812
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);
14818 });
14819 } else {
14820 selectCtrl.addOption(attr.value);
14821 }
14822
14823 element.bind('$destroy', function() {
14824 selectCtrl.removeOption(attr.value);
14825 });
14826 };
14827 }
14828 }
14829 }];
14830
14831 var styleDirective = valueFn({
14832 restrict: 'E',
14833 terminal: true
14834 });
14835
14836 //try to bind to jquery now so that one can write angular.element().read()
14837 //but we will rebind on bootstrap again.
14838 bindJQuery();
14839
14840 publishExternalAPI(angular);
14841
14842 jqLite(document).ready(function() {
14843 angularInit(document, bootstrap);
14844 });
14845
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>');