1 /******/ (function(modules) { // webpackBootstrap
2 /******/ // The module cache
3 /******/ var installedModules = {};
5 /******/ // The require function
6 /******/ function __webpack_require__(moduleId) {
8 /******/ // Check if module is in cache
9 /******/ if(installedModules[moduleId])
10 /******/ return installedModules[moduleId].exports;
12 /******/ // Create a new module (and put it into the cache)
13 /******/ var module = installedModules[moduleId] = {
15 /******/ id: moduleId,
16 /******/ loaded: false
19 /******/ // Execute the module function
20 /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
22 /******/ // Flag the module as loaded
23 /******/ module.loaded = true;
25 /******/ // Return the exports of the module
26 /******/ return module.exports;
30 /******/ // expose the modules object (__webpack_modules__)
31 /******/ __webpack_require__.m = modules;
33 /******/ // expose the module cache
34 /******/ __webpack_require__.c = installedModules;
36 /******/ // __webpack_public_path__
37 /******/ __webpack_require__.p = "";
39 /******/ // Load entry module and return exports
40 /******/ return __webpack_require__(0);
42 /************************************************************************/
45 /***/ function(module, exports, __webpack_require__) {
47 __webpack_require__(1);
48 __webpack_require__(3);
49 __webpack_require__(4);
50 __webpack_require__(6);
51 __webpack_require__(8);
52 __webpack_require__(9);
53 __webpack_require__(10);
54 __webpack_require__(11);
55 __webpack_require__(12);
56 __webpack_require__(13);
57 __webpack_require__(14);
58 __webpack_require__(15);
59 __webpack_require__(16);
60 __webpack_require__(17);
65 /***/ function(module, exports, __webpack_require__) {
67 __webpack_require__(2);
68 module.exports = angular;
73 /***/ function(module, exports) {
76 * @license AngularJS v1.4.8
77 * (c) 2010-2015 Google, Inc. http://angularjs.org
80 (function(window, document, undefined) {'use strict';
85 * This object provides a utility for producing rich Error messages within
86 * Angular. It can be called as follows:
88 * var exampleMinErr = minErr('example');
89 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
91 * The above creates an instance of minErr in the example namespace. The
92 * resulting error will have a namespaced error code of example.one. The
93 * resulting error will replace {0} with the value of foo, and {1} with the
94 * value of bar. The object is not restricted in the number of arguments it can
97 * If fewer arguments are specified than necessary for interpolation, the extra
98 * interpolation markers will be preserved in the final string.
100 * Since data will be parsed statically during a build step, some restrictions
101 * are applied with respect to how minErr instances are created and called.
102 * Instances should have names of the form namespaceMinErr for a minErr created
103 * using minErr('namespace') . Error codes, namespaces and template strings
104 * should all be static strings, not variables or general expressions.
106 * @param {string} module The namespace to use for the new minErr instance.
107 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
108 * error from returned function, for cases when a particular type of error is useful.
109 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
112 function minErr(module, ErrorConstructor) {
113 ErrorConstructor = ErrorConstructor || Error;
115 var SKIP_INDEXES = 2;
117 var templateArgs = arguments,
118 code = templateArgs[0],
119 message = '[' + (module ? module + ':' : '') + code + '] ',
120 template = templateArgs[1],
123 message += template.replace(/\{\d+\}/g, function(match) {
124 var index = +match.slice(1, -1),
125 shiftedIndex = index + SKIP_INDEXES;
127 if (shiftedIndex < templateArgs.length) {
128 return toDebugString(templateArgs[shiftedIndex]);
134 message += '\nhttp://errors.angularjs.org/1.4.8/' +
135 (module ? module + '/' : '') + code;
137 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
138 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
139 encodeURIComponent(toDebugString(templateArgs[i]));
142 return new ErrorConstructor(message);
146 /* We need to tell jshint what variables are being exported */
147 /* global angular: true,
158 REGEX_STRING_REGEXP: true,
159 VALIDITY_STATE_PROPERTY: true,
163 manualLowercase: true,
164 manualUppercase: true,
197 escapeForRegexp: true,
210 toJsonReplacer: true,
213 convertTimezoneToLocal: true,
214 timezoneToOffset: true,
216 tryDecodeURIComponent: true,
219 encodeUriSegment: true,
220 encodeUriQuery: true,
223 getTestability: true,
228 assertNotHasOwnProperty: true,
231 hasOwnProperty: true,
234 NODE_TYPE_ELEMENT: true,
235 NODE_TYPE_ATTRIBUTE: true,
236 NODE_TYPE_TEXT: true,
237 NODE_TYPE_COMMENT: true,
238 NODE_TYPE_DOCUMENT: true,
239 NODE_TYPE_DOCUMENT_FRAGMENT: true,
242 ////////////////////////////////////
251 * The ng module is loaded by default when an AngularJS application is started. The module itself
252 * contains the essential components for an AngularJS application to function. The table below
253 * lists a high level breakdown of each of the services/factories, filters, directives and testing
254 * components available within this core module.
256 * <div doc-module-components="ng"></div>
259 var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
261 // The name of a form control's ValidityState property.
262 // This is used so that it's possible for internal tests to create mock ValidityStates.
263 var VALIDITY_STATE_PROPERTY = 'validity';
267 * @name angular.lowercase
271 * @description Converts the specified string to lowercase.
272 * @param {string} string String to be converted to lowercase.
273 * @returns {string} Lowercased string.
275 var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
276 var hasOwnProperty = Object.prototype.hasOwnProperty;
280 * @name angular.uppercase
284 * @description Converts the specified string to uppercase.
285 * @param {string} string String to be converted to uppercase.
286 * @returns {string} Uppercased string.
288 var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
291 var manualLowercase = function(s) {
292 /* jshint bitwise: false */
294 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
297 var manualUppercase = function(s) {
298 /* jshint bitwise: false */
300 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
305 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
306 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
307 // with correct but slower alternatives.
308 if ('i' !== 'I'.toLowerCase()) {
309 lowercase = manualLowercase;
310 uppercase = manualUppercase;
315 msie, // holds major version number for IE, or NaN if UA is not IE.
316 jqLite, // delay binding since jQuery could be loaded after us.
317 jQuery, // delay binding
321 toString = Object.prototype.toString,
322 getPrototypeOf = Object.getPrototypeOf,
323 ngMinErr = minErr('ng'),
326 angular = window.angular || (window.angular = {}),
331 * documentMode is an IE-only property
332 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
334 msie = document.documentMode;
340 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
343 function isArrayLike(obj) {
345 // `null`, `undefined` and `window` are not array-like
346 if (obj == null || isWindow(obj)) return false;
348 // arrays, strings and jQuery/jqLite objects are array like
349 // * jqLite is either the jQuery or jqLite constructor function
350 // * we have to check the existance of jqLite first as this method is called
351 // via the forEach method when constructing the jqLite object in the first place
352 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
354 // Support: iOS 8.2 (not reproducible in simulator)
355 // "length" in obj used to prevent JIT error (gh-11508)
356 var length = "length" in Object(obj) && obj.length;
358 // NodeList objects (with `item` method) and
359 // other objects with suitable length characteristics are array-like
360 return isNumber(length) &&
361 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
366 * @name angular.forEach
371 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
372 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
373 * is the value of an object property or an array element, `key` is the object property key or
374 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
376 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
377 * using the `hasOwnProperty` method.
380 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
381 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
382 * return the value provided.
385 var values = {name: 'misko', gender: 'male'};
387 angular.forEach(values, function(value, key) {
388 this.push(key + ': ' + value);
390 expect(log).toEqual(['name: misko', 'gender: male']);
393 * @param {Object|Array} obj Object to iterate over.
394 * @param {Function} iterator Iterator function.
395 * @param {Object=} context Object to become context (`this`) for the iterator function.
396 * @returns {Object|Array} Reference to `obj`.
399 function forEach(obj, iterator, context) {
402 if (isFunction(obj)) {
404 // Need to check if hasOwnProperty exists,
405 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
406 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
407 iterator.call(context, obj[key], key, obj);
410 } else if (isArray(obj) || isArrayLike(obj)) {
411 var isPrimitive = typeof obj !== 'object';
412 for (key = 0, length = obj.length; key < length; key++) {
413 if (isPrimitive || key in obj) {
414 iterator.call(context, obj[key], key, obj);
417 } else if (obj.forEach && obj.forEach !== forEach) {
418 obj.forEach(iterator, context, obj);
419 } else if (isBlankObject(obj)) {
420 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
422 iterator.call(context, obj[key], key, obj);
424 } else if (typeof obj.hasOwnProperty === 'function') {
425 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
427 if (obj.hasOwnProperty(key)) {
428 iterator.call(context, obj[key], key, obj);
432 // Slow path for objects which do not have a method `hasOwnProperty`
434 if (hasOwnProperty.call(obj, key)) {
435 iterator.call(context, obj[key], key, obj);
443 function forEachSorted(obj, iterator, context) {
444 var keys = Object.keys(obj).sort();
445 for (var i = 0; i < keys.length; i++) {
446 iterator.call(context, obj[keys[i]], keys[i]);
453 * when using forEach the params are value, key, but it is often useful to have key, value.
454 * @param {function(string, *)} iteratorFn
455 * @returns {function(*, string)}
457 function reverseParams(iteratorFn) {
458 return function(value, key) { iteratorFn(key, value); };
462 * A consistent way of creating unique IDs in angular.
464 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
465 * we hit number precision issues in JavaScript.
467 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
469 * @returns {number} an unique alpha-numeric string
477 * Set or clear the hashkey for an object.
479 * @param h the hashkey (!truthy to delete the hashkey)
481 function setHashKey(obj, h) {
485 delete obj.$$hashKey;
490 function baseExtend(dst, objs, deep) {
491 var h = dst.$$hashKey;
493 for (var i = 0, ii = objs.length; i < ii; ++i) {
495 if (!isObject(obj) && !isFunction(obj)) continue;
496 var keys = Object.keys(obj);
497 for (var j = 0, jj = keys.length; j < jj; j++) {
501 if (deep && isObject(src)) {
503 dst[key] = new Date(src.valueOf());
504 } else if (isRegExp(src)) {
505 dst[key] = new RegExp(src);
506 } else if (src.nodeName) {
507 dst[key] = src.cloneNode(true);
508 } else if (isElement(src)) {
509 dst[key] = src.clone();
511 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
512 baseExtend(dst[key], [src], true);
526 * @name angular.extend
531 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
532 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
533 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
535 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
536 * {@link angular.merge} for this.
538 * @param {Object} dst Destination object.
539 * @param {...Object} src Source object(s).
540 * @returns {Object} Reference to `dst`.
542 function extend(dst) {
543 return baseExtend(dst, slice.call(arguments, 1), false);
549 * @name angular.merge
554 * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
555 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
556 * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
558 * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
559 * objects, performing a deep copy.
561 * @param {Object} dst Destination object.
562 * @param {...Object} src Source object(s).
563 * @returns {Object} Reference to `dst`.
565 function merge(dst) {
566 return baseExtend(dst, slice.call(arguments, 1), true);
571 function toInt(str) {
572 return parseInt(str, 10);
576 function inherit(parent, extra) {
577 return extend(Object.create(parent), extra);
587 * A function that performs no operations. This function can be useful when writing code in the
590 function foo(callback) {
591 var result = calculateResult();
592 (callback || angular.noop)(result);
602 * @name angular.identity
607 * A function that returns its first argument. This function is useful when writing code in the
611 function transformer(transformationFn, value) {
612 return (transformationFn || angular.identity)(value);
615 * @param {*} value to be returned.
616 * @returns {*} the value passed in.
618 function identity($) {return $;}
619 identity.$inject = [];
622 function valueFn(value) {return function() {return value;};}
624 function hasCustomToString(obj) {
625 return isFunction(obj.toString) && obj.toString !== toString;
631 * @name angular.isUndefined
636 * Determines if a reference is undefined.
638 * @param {*} value Reference to check.
639 * @returns {boolean} True if `value` is undefined.
641 function isUndefined(value) {return typeof value === 'undefined';}
646 * @name angular.isDefined
651 * Determines if a reference is defined.
653 * @param {*} value Reference to check.
654 * @returns {boolean} True if `value` is defined.
656 function isDefined(value) {return typeof value !== 'undefined';}
661 * @name angular.isObject
666 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
667 * considered to be objects. Note that JavaScript arrays are objects.
669 * @param {*} value Reference to check.
670 * @returns {boolean} True if `value` is an `Object` but not `null`.
672 function isObject(value) {
673 // http://jsperf.com/isobject4
674 return value !== null && typeof value === 'object';
679 * Determine if a value is an object with a null prototype
681 * @returns {boolean} True if `value` is an `Object` with a null prototype
683 function isBlankObject(value) {
684 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
690 * @name angular.isString
695 * Determines if a reference is a `String`.
697 * @param {*} value Reference to check.
698 * @returns {boolean} True if `value` is a `String`.
700 function isString(value) {return typeof value === 'string';}
705 * @name angular.isNumber
710 * Determines if a reference is a `Number`.
712 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
714 * If you wish to exclude these then you can use the native
715 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
718 * @param {*} value Reference to check.
719 * @returns {boolean} True if `value` is a `Number`.
721 function isNumber(value) {return typeof value === 'number';}
726 * @name angular.isDate
731 * Determines if a value is a date.
733 * @param {*} value Reference to check.
734 * @returns {boolean} True if `value` is a `Date`.
736 function isDate(value) {
737 return toString.call(value) === '[object Date]';
743 * @name angular.isArray
748 * Determines if a reference is an `Array`.
750 * @param {*} value Reference to check.
751 * @returns {boolean} True if `value` is an `Array`.
753 var isArray = Array.isArray;
757 * @name angular.isFunction
762 * Determines if a reference is a `Function`.
764 * @param {*} value Reference to check.
765 * @returns {boolean} True if `value` is a `Function`.
767 function isFunction(value) {return typeof value === 'function';}
771 * Determines if a value is a regular expression object.
774 * @param {*} value Reference to check.
775 * @returns {boolean} True if `value` is a `RegExp`.
777 function isRegExp(value) {
778 return toString.call(value) === '[object RegExp]';
783 * Checks if `obj` is a window object.
786 * @param {*} obj Object to check
787 * @returns {boolean} True if `obj` is a window obj.
789 function isWindow(obj) {
790 return obj && obj.window === obj;
794 function isScope(obj) {
795 return obj && obj.$evalAsync && obj.$watch;
799 function isFile(obj) {
800 return toString.call(obj) === '[object File]';
804 function isFormData(obj) {
805 return toString.call(obj) === '[object FormData]';
809 function isBlob(obj) {
810 return toString.call(obj) === '[object Blob]';
814 function isBoolean(value) {
815 return typeof value === 'boolean';
819 function isPromiseLike(obj) {
820 return obj && isFunction(obj.then);
824 var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
825 function isTypedArray(value) {
826 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
830 var trim = function(value) {
831 return isString(value) ? value.trim() : value;
835 // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
836 // Prereq: s is a string.
837 var escapeForRegexp = function(s) {
838 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
839 replace(/\x08/g, '\\x08');
845 * @name angular.isElement
850 * Determines if a reference is a DOM element (or wrapped jQuery element).
852 * @param {*} value Reference to check.
853 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
855 function isElement(node) {
857 (node.nodeName // we are a direct element
858 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
862 * @param str 'key1,key2,...'
863 * @returns {object} in the form of {key1:true, key2:true, ...}
865 function makeMap(str) {
866 var obj = {}, items = str.split(","), i;
867 for (i = 0; i < items.length; i++) {
868 obj[items[i]] = true;
874 function nodeName_(element) {
875 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
878 function includes(array, obj) {
879 return Array.prototype.indexOf.call(array, obj) != -1;
882 function arrayRemove(array, value) {
883 var index = array.indexOf(value);
885 array.splice(index, 1);
897 * Creates a deep copy of `source`, which should be an object or an array.
899 * * If no destination is supplied, a copy of the object or array is created.
900 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
901 * are deleted and then all elements/properties from the source are copied to it.
902 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
903 * * If `source` is identical to 'destination' an exception will be thrown.
905 * @param {*} source The source that will be used to make a copy.
906 * Can be any type, including primitives, `null`, and `undefined`.
907 * @param {(Object|Array)=} destination Destination into which the source is copied. If
908 * provided, must be of the same type as `source`.
909 * @returns {*} The copy or updated `destination`, if `destination` was specified.
912 <example module="copyExample">
913 <file name="index.html">
914 <div ng-controller="ExampleController">
915 <form novalidate class="simple-form">
916 Name: <input type="text" ng-model="user.name" /><br />
917 E-mail: <input type="email" ng-model="user.email" /><br />
918 Gender: <input type="radio" ng-model="user.gender" value="male" />male
919 <input type="radio" ng-model="user.gender" value="female" />female<br />
920 <button ng-click="reset()">RESET</button>
921 <button ng-click="update(user)">SAVE</button>
923 <pre>form = {{user | json}}</pre>
924 <pre>master = {{master | json}}</pre>
928 angular.module('copyExample', [])
929 .controller('ExampleController', ['$scope', function($scope) {
932 $scope.update = function(user) {
933 // Example with 1 argument
934 $scope.master= angular.copy(user);
937 $scope.reset = function() {
938 // Example with 2 arguments
939 angular.copy($scope.master, $scope.user);
948 function copy(source, destination) {
949 var stackSource = [];
953 if (isTypedArray(destination)) {
954 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
956 if (source === destination) {
957 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
960 // Empty the destination object
961 if (isArray(destination)) {
962 destination.length = 0;
964 forEach(destination, function(value, key) {
965 if (key !== '$$hashKey') {
966 delete destination[key];
971 stackSource.push(source);
972 stackDest.push(destination);
973 return copyRecurse(source, destination);
976 return copyElement(source);
978 function copyRecurse(source, destination) {
979 var h = destination.$$hashKey;
981 if (isArray(source)) {
982 for (var i = 0, ii = source.length; i < ii; i++) {
983 destination.push(copyElement(source[i]));
985 } else if (isBlankObject(source)) {
986 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
987 for (key in source) {
988 destination[key] = copyElement(source[key]);
990 } else if (source && typeof source.hasOwnProperty === 'function') {
991 // Slow path, which must rely on hasOwnProperty
992 for (key in source) {
993 if (source.hasOwnProperty(key)) {
994 destination[key] = copyElement(source[key]);
998 // Slowest path --- hasOwnProperty can't be called as a method
999 for (key in source) {
1000 if (hasOwnProperty.call(source, key)) {
1001 destination[key] = copyElement(source[key]);
1005 setHashKey(destination, h);
1009 function copyElement(source) {
1011 if (!isObject(source)) {
1015 // Already copied values
1016 var index = stackSource.indexOf(source);
1018 return stackDest[index];
1021 if (isWindow(source) || isScope(source)) {
1022 throw ngMinErr('cpws',
1023 "Can't copy! Making copies of Window or Scope instances is not supported.");
1026 var needsRecurse = false;
1029 if (isArray(source)) {
1031 needsRecurse = true;
1032 } else if (isTypedArray(source)) {
1033 destination = new source.constructor(source);
1034 } else if (isDate(source)) {
1035 destination = new Date(source.getTime());
1036 } else if (isRegExp(source)) {
1037 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
1038 destination.lastIndex = source.lastIndex;
1039 } else if (isFunction(source.cloneNode)) {
1040 destination = source.cloneNode(true);
1042 destination = Object.create(getPrototypeOf(source));
1043 needsRecurse = true;
1046 stackSource.push(source);
1047 stackDest.push(destination);
1050 ? copyRecurse(source, destination)
1056 * Creates a shallow copy of an object, an array or a primitive.
1058 * Assumes that there are no proto properties for objects.
1060 function shallowCopy(src, dst) {
1064 for (var i = 0, ii = src.length; i < ii; i++) {
1067 } else if (isObject(src)) {
1070 for (var key in src) {
1071 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1072 dst[key] = src[key];
1083 * @name angular.equals
1088 * Determines if two objects or two values are equivalent. Supports value types, regular
1089 * expressions, arrays and objects.
1091 * Two objects or values are considered equivalent if at least one of the following is true:
1093 * * Both objects or values pass `===` comparison.
1094 * * Both objects or values are of the same type and all of their properties are equal by
1095 * comparing them with `angular.equals`.
1096 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1097 * * Both values represent the same regular expression (In JavaScript,
1098 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1099 * representation matches).
1101 * During a property comparison, properties of `function` type and properties with names
1102 * that begin with `$` are ignored.
1104 * Scope and DOMWindow objects are being compared only by identify (`===`).
1106 * @param {*} o1 Object or value to compare.
1107 * @param {*} o2 Object or value to compare.
1108 * @returns {boolean} True if arguments are equal.
1110 function equals(o1, o2) {
1111 if (o1 === o2) return true;
1112 if (o1 === null || o2 === null) return false;
1113 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1114 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1116 if (t1 == 'object') {
1118 if (!isArray(o2)) return false;
1119 if ((length = o1.length) == o2.length) {
1120 for (key = 0; key < length; key++) {
1121 if (!equals(o1[key], o2[key])) return false;
1125 } else if (isDate(o1)) {
1126 if (!isDate(o2)) return false;
1127 return equals(o1.getTime(), o2.getTime());
1128 } else if (isRegExp(o1)) {
1129 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1131 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1132 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1133 keySet = createMap();
1135 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1136 if (!equals(o1[key], o2[key])) return false;
1140 if (!(key in keySet) &&
1141 key.charAt(0) !== '$' &&
1142 isDefined(o2[key]) &&
1143 !isFunction(o2[key])) return false;
1152 var csp = function() {
1153 if (!isDefined(csp.rules)) {
1156 var ngCspElement = (document.querySelector('[ng-csp]') ||
1157 document.querySelector('[data-ng-csp]'));
1160 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1161 ngCspElement.getAttribute('data-ng-csp');
1163 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1164 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1168 noUnsafeEval: noUnsafeEval(),
1169 noInlineStyle: false
1176 function noUnsafeEval() {
1178 /* jshint -W031, -W054 */
1180 /* jshint +W031, +W054 */
1194 * @param {string=} ngJq the name of the library available under `window`
1195 * to be used for angular.element
1197 * Use this directive to force the angular.element library. This should be
1198 * used to force either jqLite by leaving ng-jq blank or setting the name of
1199 * the jquery variable under window (eg. jQuery).
1201 * Since angular looks for this directive when it is loaded (doesn't wait for the
1202 * DOMContentLoaded event), it must be placed on an element that comes before the script
1203 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1207 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1216 * This example shows how to use a jQuery based library of a different name.
1217 * The library name must be available at the top most 'window'.
1220 <html ng-app ng-jq="jQueryLib">
1226 var jq = function() {
1227 if (isDefined(jq.name_)) return jq.name_;
1229 var i, ii = ngAttrPrefixes.length, prefix, name;
1230 for (i = 0; i < ii; ++i) {
1231 prefix = ngAttrPrefixes[i];
1232 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1233 name = el.getAttribute(prefix + 'jq');
1238 return (jq.name_ = name);
1241 function concat(array1, array2, index) {
1242 return array1.concat(slice.call(array2, index));
1245 function sliceArgs(args, startIndex) {
1246 return slice.call(args, startIndex || 0);
1253 * @name angular.bind
1258 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1259 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1260 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1261 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1263 * @param {Object} self Context which `fn` should be evaluated in.
1264 * @param {function()} fn Function to be bound.
1265 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1266 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1269 function bind(self, fn) {
1270 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1271 if (isFunction(fn) && !(fn instanceof RegExp)) {
1272 return curryArgs.length
1274 return arguments.length
1275 ? fn.apply(self, concat(curryArgs, arguments, 0))
1276 : fn.apply(self, curryArgs);
1279 return arguments.length
1280 ? fn.apply(self, arguments)
1284 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1290 function toJsonReplacer(key, value) {
1293 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1295 } else if (isWindow(value)) {
1297 } else if (value && document === value) {
1299 } else if (isScope(value)) {
1309 * @name angular.toJson
1314 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1315 * stripped since angular uses this notation internally.
1317 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1318 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1319 * If set to an integer, the JSON output will contain that many spaces per indentation.
1320 * @returns {string|undefined} JSON-ified string representing `obj`.
1322 function toJson(obj, pretty) {
1323 if (typeof obj === 'undefined') return undefined;
1324 if (!isNumber(pretty)) {
1325 pretty = pretty ? 2 : null;
1327 return JSON.stringify(obj, toJsonReplacer, pretty);
1333 * @name angular.fromJson
1338 * Deserializes a JSON string.
1340 * @param {string} json JSON string to deserialize.
1341 * @returns {Object|Array|string|number} Deserialized JSON string.
1343 function fromJson(json) {
1344 return isString(json)
1350 function timezoneToOffset(timezone, fallback) {
1351 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1352 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1356 function addDateMinutes(date, minutes) {
1357 date = new Date(date.getTime());
1358 date.setMinutes(date.getMinutes() + minutes);
1363 function convertTimezoneToLocal(date, timezone, reverse) {
1364 reverse = reverse ? -1 : 1;
1365 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1366 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1371 * @returns {string} Returns the string representation of the element.
1373 function startingTag(element) {
1374 element = jqLite(element).clone();
1376 // turns out IE does not let you set .html() on elements which
1377 // are not allowed to have children. So we just ignore it.
1380 var elemHtml = jqLite('<div>').append(element).html();
1382 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1384 match(/^(<[^>]+>)/)[1].
1385 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1387 return lowercase(elemHtml);
1393 /////////////////////////////////////////////////
1396 * Tries to decode the URI component without throwing an exception.
1399 * @param str value potential URI component to check.
1400 * @returns {boolean} True if `value` can be decoded
1401 * with the decodeURIComponent function.
1403 function tryDecodeURIComponent(value) {
1405 return decodeURIComponent(value);
1407 // Ignore any invalid uri component
1413 * Parses an escaped url query string into key-value pairs.
1414 * @returns {Object.<string,boolean|Array>}
1416 function parseKeyValue(/**string*/keyValue) {
1418 forEach((keyValue || "").split('&'), function(keyValue) {
1419 var splitPoint, key, val;
1421 key = keyValue = keyValue.replace(/\+/g,'%20');
1422 splitPoint = keyValue.indexOf('=');
1423 if (splitPoint !== -1) {
1424 key = keyValue.substring(0, splitPoint);
1425 val = keyValue.substring(splitPoint + 1);
1427 key = tryDecodeURIComponent(key);
1428 if (isDefined(key)) {
1429 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1430 if (!hasOwnProperty.call(obj, key)) {
1432 } else if (isArray(obj[key])) {
1435 obj[key] = [obj[key],val];
1443 function toKeyValue(obj) {
1445 forEach(obj, function(value, key) {
1446 if (isArray(value)) {
1447 forEach(value, function(arrayValue) {
1448 parts.push(encodeUriQuery(key, true) +
1449 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1452 parts.push(encodeUriQuery(key, true) +
1453 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1456 return parts.length ? parts.join('&') : '';
1461 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1462 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1465 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1466 * pct-encoded = "%" HEXDIG HEXDIG
1467 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1468 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1469 * / "*" / "+" / "," / ";" / "="
1471 function encodeUriSegment(val) {
1472 return encodeUriQuery(val, true).
1473 replace(/%26/gi, '&').
1474 replace(/%3D/gi, '=').
1475 replace(/%2B/gi, '+');
1480 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1481 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1482 * encoded per http://tools.ietf.org/html/rfc3986:
1483 * query = *( pchar / "/" / "?" )
1484 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1485 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1486 * pct-encoded = "%" HEXDIG HEXDIG
1487 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1488 * / "*" / "+" / "," / ";" / "="
1490 function encodeUriQuery(val, pctEncodeSpaces) {
1491 return encodeURIComponent(val).
1492 replace(/%40/gi, '@').
1493 replace(/%3A/gi, ':').
1494 replace(/%24/g, '$').
1495 replace(/%2C/gi, ',').
1496 replace(/%3B/gi, ';').
1497 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1500 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1502 function getNgAttribute(element, ngAttr) {
1503 var attr, i, ii = ngAttrPrefixes.length;
1504 for (i = 0; i < ii; ++i) {
1505 attr = ngAttrPrefixes[i] + ngAttr;
1506 if (isString(attr = element.getAttribute(attr))) {
1519 * @param {angular.Module} ngApp an optional application
1520 * {@link angular.module module} name to load.
1521 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1522 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1523 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1524 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1525 * tracking down the root of these bugs.
1529 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1530 * designates the **root element** of the application and is typically placed near the root element
1531 * of the page - e.g. on the `<body>` or `<html>` tags.
1533 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1534 * found in the document will be used to define the root element to auto-bootstrap as an
1535 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1536 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1538 * You can specify an **AngularJS module** to be used as the root module for the application. This
1539 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1540 * should contain the application code needed or have dependencies on other modules that will
1541 * contain the code. See {@link angular.module} for more information.
1543 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1544 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1545 * would not be resolved to `3`.
1547 * `ngApp` is the easiest, and most common way to bootstrap an application.
1549 <example module="ngAppDemo">
1550 <file name="index.html">
1551 <div ng-controller="ngAppDemoController">
1552 I can add: {{a}} + {{b}} = {{ a+b }}
1555 <file name="script.js">
1556 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1563 * Using `ngStrictDi`, you would see something like this:
1565 <example ng-app-included="true">
1566 <file name="index.html">
1567 <div ng-app="ngAppStrictDemo" ng-strict-di>
1568 <div ng-controller="GoodController1">
1569 I can add: {{a}} + {{b}} = {{ a+b }}
1571 <p>This renders because the controller does not fail to
1572 instantiate, by using explicit annotation style (see
1573 script.js for details)
1577 <div ng-controller="GoodController2">
1578 Name: <input ng-model="name"><br />
1581 <p>This renders because the controller does not fail to
1582 instantiate, by using explicit annotation style
1583 (see script.js for details)
1587 <div ng-controller="BadController">
1588 I can add: {{a}} + {{b}} = {{ a+b }}
1590 <p>The controller could not be instantiated, due to relying
1591 on automatic function annotations (which are disabled in
1592 strict mode). As such, the content of this section is not
1593 interpolated, and there should be an error in your web console.
1598 <file name="script.js">
1599 angular.module('ngAppStrictDemo', [])
1600 // BadController will fail to instantiate, due to relying on automatic function annotation,
1601 // rather than an explicit annotation
1602 .controller('BadController', function($scope) {
1606 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1607 // due to using explicit annotations using the array style and $inject property, respectively.
1608 .controller('GoodController1', ['$scope', function($scope) {
1612 .controller('GoodController2', GoodController2);
1613 function GoodController2($scope) {
1614 $scope.name = "World";
1616 GoodController2.$inject = ['$scope'];
1618 <file name="style.css">
1619 div[ng-controller] {
1621 -webkit-border-radius: 4px;
1626 div[ng-controller^=Good] {
1627 border-color: #d6e9c6;
1628 background-color: #dff0d8;
1631 div[ng-controller^=Bad] {
1632 border-color: #ebccd1;
1633 background-color: #f2dede;
1640 function angularInit(element, bootstrap) {
1645 // The element `element` has priority over any other element
1646 forEach(ngAttrPrefixes, function(prefix) {
1647 var name = prefix + 'app';
1649 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1650 appElement = element;
1651 module = element.getAttribute(name);
1654 forEach(ngAttrPrefixes, function(prefix) {
1655 var name = prefix + 'app';
1658 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1659 appElement = candidate;
1660 module = candidate.getAttribute(name);
1664 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1665 bootstrap(appElement, module ? [module] : [], config);
1671 * @name angular.bootstrap
1674 * Use this function to manually start up angular application.
1676 * See: {@link guide/bootstrap Bootstrap}
1678 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1679 * They must use {@link ng.directive:ngApp ngApp}.
1681 * Angular will detect if it has been loaded into the browser more than once and only allow the
1682 * first loaded script to be bootstrapped and will report a warning to the browser console for
1683 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1684 * multiple instances of Angular try to work on the DOM.
1690 * <div ng-controller="WelcomeController">
1694 * <script src="angular.js"></script>
1696 * var app = angular.module('demo', [])
1697 * .controller('WelcomeController', function($scope) {
1698 * $scope.greeting = 'Welcome!';
1700 * angular.bootstrap(document, ['demo']);
1706 * @param {DOMElement} element DOM element which is the root of angular application.
1707 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1708 * Each item in the array should be the name of a predefined module or a (DI annotated)
1709 * function that will be invoked by the injector as a `config` block.
1710 * See: {@link angular.module modules}
1711 * @param {Object=} config an object for defining configuration options for the application. The
1712 * following keys are supported:
1714 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1715 * assist in finding bugs which break minified code. Defaults to `false`.
1717 * @returns {auto.$injector} Returns the newly created injector for this app.
1719 function bootstrap(element, modules, config) {
1720 if (!isObject(config)) config = {};
1721 var defaultConfig = {
1724 config = extend(defaultConfig, config);
1725 var doBootstrap = function() {
1726 element = jqLite(element);
1728 if (element.injector()) {
1729 var tag = (element[0] === document) ? 'document' : startingTag(element);
1730 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1733 "App Already Bootstrapped with this Element '{0}'",
1734 tag.replace(/</,'<').replace(/>/,'>'));
1737 modules = modules || [];
1738 modules.unshift(['$provide', function($provide) {
1739 $provide.value('$rootElement', element);
1742 if (config.debugInfoEnabled) {
1743 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1744 modules.push(['$compileProvider', function($compileProvider) {
1745 $compileProvider.debugInfoEnabled(true);
1749 modules.unshift('ng');
1750 var injector = createInjector(modules, config.strictDi);
1751 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1752 function bootstrapApply(scope, element, compile, injector) {
1753 scope.$apply(function() {
1754 element.data('$injector', injector);
1755 compile(element)(scope);
1762 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1763 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1765 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1766 config.debugInfoEnabled = true;
1767 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1770 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1771 return doBootstrap();
1774 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1775 angular.resumeBootstrap = function(extraModules) {
1776 forEach(extraModules, function(module) {
1777 modules.push(module);
1779 return doBootstrap();
1782 if (isFunction(angular.resumeDeferredBootstrap)) {
1783 angular.resumeDeferredBootstrap();
1789 * @name angular.reloadWithDebugInfo
1792 * Use this function to reload the current application with debug information turned on.
1793 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1795 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1797 function reloadWithDebugInfo() {
1798 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1799 window.location.reload();
1803 * @name angular.getTestability
1806 * Get the testability service for the instance of Angular on the given
1808 * @param {DOMElement} element DOM element which is the root of angular application.
1810 function getTestability(rootElement) {
1811 var injector = angular.element(rootElement).injector();
1813 throw ngMinErr('test',
1814 'no injector found for element argument to getTestability');
1816 return injector.get('$$testability');
1819 var SNAKE_CASE_REGEXP = /[A-Z]/g;
1820 function snake_case(name, separator) {
1821 separator = separator || '_';
1822 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1823 return (pos ? separator : '') + letter.toLowerCase();
1827 var bindJQueryFired = false;
1828 var skipDestroyOnNextJQueryCleanData;
1829 function bindJQuery() {
1830 var originalCleanData;
1832 if (bindJQueryFired) {
1836 // bind to jQuery if present;
1838 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1839 !jqName ? undefined : // use jqLite
1840 window[jqName]; // use jQuery specified by `ngJq`
1842 // Use jQuery if it exists with proper functionality, otherwise default to us.
1843 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1844 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1845 // versions. It will not work for sure with jQuery <1.7, though.
1846 if (jQuery && jQuery.fn.on) {
1849 scope: JQLitePrototype.scope,
1850 isolateScope: JQLitePrototype.isolateScope,
1851 controller: JQLitePrototype.controller,
1852 injector: JQLitePrototype.injector,
1853 inheritedData: JQLitePrototype.inheritedData
1856 // All nodes removed from the DOM via various jQuery APIs like .remove()
1857 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1858 // the $destroy event on all removed nodes.
1859 originalCleanData = jQuery.cleanData;
1860 jQuery.cleanData = function(elems) {
1862 if (!skipDestroyOnNextJQueryCleanData) {
1863 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1864 events = jQuery._data(elem, "events");
1865 if (events && events.$destroy) {
1866 jQuery(elem).triggerHandler('$destroy');
1870 skipDestroyOnNextJQueryCleanData = false;
1872 originalCleanData(elems);
1878 angular.element = jqLite;
1880 // Prevent double-proxying.
1881 bindJQueryFired = true;
1885 * throw error if the argument is falsy.
1887 function assertArg(arg, name, reason) {
1889 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1894 function assertArgFn(arg, name, acceptArrayAnnotation) {
1895 if (acceptArrayAnnotation && isArray(arg)) {
1896 arg = arg[arg.length - 1];
1899 assertArg(isFunction(arg), name, 'not a function, got ' +
1900 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1905 * throw error if the name given is hasOwnProperty
1906 * @param {String} name the name to test
1907 * @param {String} context the context in which the name is used, such as module or directive
1909 function assertNotHasOwnProperty(name, context) {
1910 if (name === 'hasOwnProperty') {
1911 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1916 * Return the value accessible from the object by path. Any undefined traversals are ignored
1917 * @param {Object} obj starting object
1918 * @param {String} path path to traverse
1919 * @param {boolean} [bindFnToScope=true]
1920 * @returns {Object} value as accessible by path
1922 //TODO(misko): this function needs to be removed
1923 function getter(obj, path, bindFnToScope) {
1924 if (!path) return obj;
1925 var keys = path.split('.');
1927 var lastInstance = obj;
1928 var len = keys.length;
1930 for (var i = 0; i < len; i++) {
1933 obj = (lastInstance = obj)[key];
1936 if (!bindFnToScope && isFunction(obj)) {
1937 return bind(lastInstance, obj);
1943 * Return the DOM siblings between the first and last node in the given array.
1944 * @param {Array} array like object
1945 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1947 function getBlockNodes(nodes) {
1948 // TODO(perf): update `nodes` instead of creating a new object?
1949 var node = nodes[0];
1950 var endNode = nodes[nodes.length - 1];
1953 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1954 if (blockNodes || nodes[i] !== node) {
1956 blockNodes = jqLite(slice.call(nodes, 0, i));
1958 blockNodes.push(node);
1962 return blockNodes || nodes;
1967 * Creates a new object without a prototype. This object is useful for lookup without having to
1968 * guard against prototypically inherited properties via hasOwnProperty.
1970 * Related micro-benchmarks:
1971 * - http://jsperf.com/object-create2
1972 * - http://jsperf.com/proto-map-lookup/2
1973 * - http://jsperf.com/for-in-vs-object-keys2
1977 function createMap() {
1978 return Object.create(null);
1981 var NODE_TYPE_ELEMENT = 1;
1982 var NODE_TYPE_ATTRIBUTE = 2;
1983 var NODE_TYPE_TEXT = 3;
1984 var NODE_TYPE_COMMENT = 8;
1985 var NODE_TYPE_DOCUMENT = 9;
1986 var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1990 * @name angular.Module
1994 * Interface for configuring angular {@link angular.module modules}.
1997 function setupModuleLoader(window) {
1999 var $injectorMinErr = minErr('$injector');
2000 var ngMinErr = minErr('ng');
2002 function ensure(obj, name, factory) {
2003 return obj[name] || (obj[name] = factory());
2006 var angular = ensure(window, 'angular', Object);
2008 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2009 angular.$$minErr = angular.$$minErr || minErr;
2011 return ensure(angular, 'module', function() {
2012 /** @type {Object.<string, angular.Module>} */
2017 * @name angular.module
2021 * The `angular.module` is a global place for creating, registering and retrieving Angular
2023 * All modules (angular core or 3rd party) that should be available to an application must be
2024 * registered using this mechanism.
2026 * Passing one argument retrieves an existing {@link angular.Module},
2027 * whereas passing more than one argument creates a new {@link angular.Module}
2032 * A module is a collection of services, directives, controllers, filters, and configuration information.
2033 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2036 * // Create a new module
2037 * var myModule = angular.module('myModule', []);
2039 * // register a new service
2040 * myModule.value('appName', 'MyCoolApp');
2042 * // configure existing services inside initialization blocks.
2043 * myModule.config(['$locationProvider', function($locationProvider) {
2044 * // Configure existing providers
2045 * $locationProvider.hashPrefix('!');
2049 * Then you can create an injector and load your modules like this:
2052 * var injector = angular.injector(['ng', 'myModule'])
2055 * However it's more likely that you'll just use
2056 * {@link ng.directive:ngApp ngApp} or
2057 * {@link angular.bootstrap} to simplify this process for you.
2059 * @param {!string} name The name of the module to create or retrieve.
2060 * @param {!Array.<string>=} requires If specified then new module is being created. If
2061 * unspecified then the module is being retrieved for further configuration.
2062 * @param {Function=} configFn Optional configuration function for the module. Same as
2063 * {@link angular.Module#config Module#config()}.
2064 * @returns {module} new module with the {@link angular.Module} api.
2066 return function module(name, requires, configFn) {
2067 var assertNotHasOwnProperty = function(name, context) {
2068 if (name === 'hasOwnProperty') {
2069 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2073 assertNotHasOwnProperty(name, 'module');
2074 if (requires && modules.hasOwnProperty(name)) {
2075 modules[name] = null;
2077 return ensure(modules, name, function() {
2079 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2080 "the module name or forgot to load it. If registering a module ensure that you " +
2081 "specify the dependencies as the second argument.", name);
2084 /** @type {!Array.<Array.<*>>} */
2085 var invokeQueue = [];
2087 /** @type {!Array.<Function>} */
2088 var configBlocks = [];
2090 /** @type {!Array.<Function>} */
2093 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2095 /** @type {angular.Module} */
2096 var moduleInstance = {
2098 _invokeQueue: invokeQueue,
2099 _configBlocks: configBlocks,
2100 _runBlocks: runBlocks,
2104 * @name angular.Module#requires
2108 * Holds the list of modules which the injector will load before the current module is
2115 * @name angular.Module#name
2119 * Name of the module.
2126 * @name angular.Module#provider
2128 * @param {string} name service name
2129 * @param {Function} providerType Construction function for creating new instance of the
2132 * See {@link auto.$provide#provider $provide.provider()}.
2134 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2138 * @name angular.Module#factory
2140 * @param {string} name service name
2141 * @param {Function} providerFunction Function for creating new instance of the service.
2143 * See {@link auto.$provide#factory $provide.factory()}.
2145 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2149 * @name angular.Module#service
2151 * @param {string} name service name
2152 * @param {Function} constructor A constructor function that will be instantiated.
2154 * See {@link auto.$provide#service $provide.service()}.
2156 service: invokeLaterAndSetModuleName('$provide', 'service'),
2160 * @name angular.Module#value
2162 * @param {string} name service name
2163 * @param {*} object Service instance object.
2165 * See {@link auto.$provide#value $provide.value()}.
2167 value: invokeLater('$provide', 'value'),
2171 * @name angular.Module#constant
2173 * @param {string} name constant name
2174 * @param {*} object Constant value.
2176 * Because the constants are fixed, they get applied before other provide methods.
2177 * See {@link auto.$provide#constant $provide.constant()}.
2179 constant: invokeLater('$provide', 'constant', 'unshift'),
2183 * @name angular.Module#decorator
2185 * @param {string} The name of the service to decorate.
2186 * @param {Function} This function will be invoked when the service needs to be
2187 * instantiated and should return the decorated service instance.
2189 * See {@link auto.$provide#decorator $provide.decorator()}.
2191 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2195 * @name angular.Module#animation
2197 * @param {string} name animation name
2198 * @param {Function} animationFactory Factory function for creating new instance of an
2202 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2205 * Defines an animation hook that can be later used with
2206 * {@link $animate $animate} service and directives that use this service.
2209 * module.animation('.animation-name', function($inject1, $inject2) {
2211 * eventName : function(element, done) {
2212 * //code to run the animation
2213 * //once complete, then run done()
2214 * return function cancellationFunction(element) {
2215 * //code to cancel the animation
2222 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2223 * {@link ngAnimate ngAnimate module} for more information.
2225 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2229 * @name angular.Module#filter
2231 * @param {string} name Filter name - this must be a valid angular expression identifier
2232 * @param {Function} filterFactory Factory function for creating new instance of filter.
2234 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2236 * <div class="alert alert-warning">
2237 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2238 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2239 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2240 * (`myapp_subsection_filterx`).
2243 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2247 * @name angular.Module#controller
2249 * @param {string|Object} name Controller name, or an object map of controllers where the
2250 * keys are the names and the values are the constructors.
2251 * @param {Function} constructor Controller constructor function.
2253 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2255 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2259 * @name angular.Module#directive
2261 * @param {string|Object} name Directive name, or an object map of directives where the
2262 * keys are the names and the values are the factories.
2263 * @param {Function} directiveFactory Factory function for creating new instance of
2266 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2268 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2272 * @name angular.Module#config
2274 * @param {Function} configFn Execute this function on module load. Useful for service
2277 * Use this method to register work which needs to be performed on module loading.
2278 * For more about how to configure services, see
2279 * {@link providers#provider-recipe Provider Recipe}.
2285 * @name angular.Module#run
2287 * @param {Function} initializationFn Execute this function after injector creation.
2288 * Useful for application initialization.
2290 * Use this method to register work which should be performed when the injector is done
2291 * loading all modules.
2293 run: function(block) {
2294 runBlocks.push(block);
2303 return moduleInstance;
2306 * @param {string} provider
2307 * @param {string} method
2308 * @param {String=} insertMethod
2309 * @returns {angular.Module}
2311 function invokeLater(provider, method, insertMethod, queue) {
2312 if (!queue) queue = invokeQueue;
2314 queue[insertMethod || 'push']([provider, method, arguments]);
2315 return moduleInstance;
2320 * @param {string} provider
2321 * @param {string} method
2322 * @returns {angular.Module}
2324 function invokeLaterAndSetModuleName(provider, method) {
2325 return function(recipeName, factoryFunction) {
2326 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2327 invokeQueue.push([provider, method, arguments]);
2328 return moduleInstance;
2337 /* global: toDebugString: true */
2339 function serializeObject(obj) {
2342 return JSON.stringify(obj, function(key, val) {
2343 val = toJsonReplacer(key, val);
2344 if (isObject(val)) {
2346 if (seen.indexOf(val) >= 0) return '...';
2354 function toDebugString(obj) {
2355 if (typeof obj === 'function') {
2356 return obj.toString().replace(/ \{[\s\S]*$/, '');
2357 } else if (isUndefined(obj)) {
2359 } else if (typeof obj !== 'string') {
2360 return serializeObject(obj);
2365 /* global angularModule: true,
2370 htmlAnchorDirective,
2379 ngBindHtmlDirective,
2380 ngBindTemplateDirective,
2382 ngClassEvenDirective,
2383 ngClassOddDirective,
2385 ngControllerDirective,
2390 ngIncludeFillContentDirective,
2392 ngNonBindableDirective,
2393 ngPluralizeDirective,
2398 ngSwitchWhenDirective,
2399 ngSwitchDefaultDirective,
2401 ngTranscludeDirective,
2414 ngModelOptionsDirective,
2415 ngAttributeAliasDirectives,
2418 $AnchorScrollProvider,
2420 $CoreAnimateCssProvider,
2421 $$CoreAnimateQueueProvider,
2422 $$CoreAnimateRunnerProvider,
2424 $CacheFactoryProvider,
2425 $ControllerProvider,
2427 $ExceptionHandlerProvider,
2429 $$ForceReflowProvider,
2430 $InterpolateProvider,
2434 $HttpParamSerializerProvider,
2435 $HttpParamSerializerJQLikeProvider,
2436 $HttpBackendProvider,
2437 $xhrFactoryProvider,
2444 $$SanitizeUriProvider,
2446 $SceDelegateProvider,
2448 $TemplateCacheProvider,
2449 $TemplateRequestProvider,
2450 $$TestabilityProvider,
2455 $$CookieReaderProvider
2461 * @name angular.version
2464 * An object that contains information about the current AngularJS version.
2466 * This object has the following properties:
2468 * - `full` – `{string}` – Full version string, such as "0.9.18".
2469 * - `major` – `{number}` – Major version number, such as "0".
2470 * - `minor` – `{number}` – Minor version number, such as "9".
2471 * - `dot` – `{number}` – Dot version number, such as "18".
2472 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2475 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2476 major: 1, // package task
2479 codeName: 'ice-manipulation'
2483 function publishExternalAPI(angular) {
2485 'bootstrap': bootstrap,
2492 'injector': createInjector,
2496 'fromJson': fromJson,
2497 'identity': identity,
2498 'isUndefined': isUndefined,
2499 'isDefined': isDefined,
2500 'isString': isString,
2501 'isFunction': isFunction,
2502 'isObject': isObject,
2503 'isNumber': isNumber,
2504 'isElement': isElement,
2508 'lowercase': lowercase,
2509 'uppercase': uppercase,
2510 'callbacks': {counter: 0},
2511 'getTestability': getTestability,
2514 'reloadWithDebugInfo': reloadWithDebugInfo
2517 angularModule = setupModuleLoader(window);
2519 angularModule('ng', ['ngLocale'], ['$provide',
2520 function ngModule($provide) {
2521 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2523 $$sanitizeUri: $$SanitizeUriProvider
2525 $provide.provider('$compile', $CompileProvider).
2527 a: htmlAnchorDirective,
2528 input: inputDirective,
2529 textarea: inputDirective,
2530 form: formDirective,
2531 script: scriptDirective,
2532 select: selectDirective,
2533 style: styleDirective,
2534 option: optionDirective,
2535 ngBind: ngBindDirective,
2536 ngBindHtml: ngBindHtmlDirective,
2537 ngBindTemplate: ngBindTemplateDirective,
2538 ngClass: ngClassDirective,
2539 ngClassEven: ngClassEvenDirective,
2540 ngClassOdd: ngClassOddDirective,
2541 ngCloak: ngCloakDirective,
2542 ngController: ngControllerDirective,
2543 ngForm: ngFormDirective,
2544 ngHide: ngHideDirective,
2545 ngIf: ngIfDirective,
2546 ngInclude: ngIncludeDirective,
2547 ngInit: ngInitDirective,
2548 ngNonBindable: ngNonBindableDirective,
2549 ngPluralize: ngPluralizeDirective,
2550 ngRepeat: ngRepeatDirective,
2551 ngShow: ngShowDirective,
2552 ngStyle: ngStyleDirective,
2553 ngSwitch: ngSwitchDirective,
2554 ngSwitchWhen: ngSwitchWhenDirective,
2555 ngSwitchDefault: ngSwitchDefaultDirective,
2556 ngOptions: ngOptionsDirective,
2557 ngTransclude: ngTranscludeDirective,
2558 ngModel: ngModelDirective,
2559 ngList: ngListDirective,
2560 ngChange: ngChangeDirective,
2561 pattern: patternDirective,
2562 ngPattern: patternDirective,
2563 required: requiredDirective,
2564 ngRequired: requiredDirective,
2565 minlength: minlengthDirective,
2566 ngMinlength: minlengthDirective,
2567 maxlength: maxlengthDirective,
2568 ngMaxlength: maxlengthDirective,
2569 ngValue: ngValueDirective,
2570 ngModelOptions: ngModelOptionsDirective
2573 ngInclude: ngIncludeFillContentDirective
2575 directive(ngAttributeAliasDirectives).
2576 directive(ngEventDirectives);
2578 $anchorScroll: $AnchorScrollProvider,
2579 $animate: $AnimateProvider,
2580 $animateCss: $CoreAnimateCssProvider,
2581 $$animateQueue: $$CoreAnimateQueueProvider,
2582 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2583 $browser: $BrowserProvider,
2584 $cacheFactory: $CacheFactoryProvider,
2585 $controller: $ControllerProvider,
2586 $document: $DocumentProvider,
2587 $exceptionHandler: $ExceptionHandlerProvider,
2588 $filter: $FilterProvider,
2589 $$forceReflow: $$ForceReflowProvider,
2590 $interpolate: $InterpolateProvider,
2591 $interval: $IntervalProvider,
2592 $http: $HttpProvider,
2593 $httpParamSerializer: $HttpParamSerializerProvider,
2594 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2595 $httpBackend: $HttpBackendProvider,
2596 $xhrFactory: $xhrFactoryProvider,
2597 $location: $LocationProvider,
2599 $parse: $ParseProvider,
2600 $rootScope: $RootScopeProvider,
2604 $sceDelegate: $SceDelegateProvider,
2605 $sniffer: $SnifferProvider,
2606 $templateCache: $TemplateCacheProvider,
2607 $templateRequest: $TemplateRequestProvider,
2608 $$testability: $$TestabilityProvider,
2609 $timeout: $TimeoutProvider,
2610 $window: $WindowProvider,
2611 $$rAF: $$RAFProvider,
2612 $$jqLite: $$jqLiteProvider,
2613 $$HashMap: $$HashMapProvider,
2614 $$cookieReader: $$CookieReaderProvider
2620 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2621 * Any commits to this file should be reviewed with security in mind. *
2622 * Changes to this file can potentially create security vulnerabilities. *
2623 * An approval from 2 Core members with history of modifying *
2624 * this file is required. *
2626 * Does the change somehow allow for arbitrary javascript to be executed? *
2627 * Or allows for someone to change the prototype of built-in objects? *
2628 * Or gives undesired access to variables likes document or window? *
2629 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2631 /* global JQLitePrototype: true,
2632 addEventListenerFn: true,
2633 removeEventListenerFn: true,
2638 //////////////////////////////////
2640 //////////////////////////////////
2644 * @name angular.element
2649 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2651 * If jQuery is available, `angular.element` is an alias for the
2652 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2653 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2655 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2656 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2657 * commonly needed functionality with the goal of having a very small footprint.</div>
2659 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2661 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2662 * jqLite; they are never raw DOM references.</div>
2664 * ## Angular's jqLite
2665 * jqLite provides only the following jQuery methods:
2667 * - [`addClass()`](http://api.jquery.com/addClass/)
2668 * - [`after()`](http://api.jquery.com/after/)
2669 * - [`append()`](http://api.jquery.com/append/)
2670 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2671 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2672 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2673 * - [`clone()`](http://api.jquery.com/clone/)
2674 * - [`contents()`](http://api.jquery.com/contents/)
2675 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
2676 * - [`data()`](http://api.jquery.com/data/)
2677 * - [`detach()`](http://api.jquery.com/detach/)
2678 * - [`empty()`](http://api.jquery.com/empty/)
2679 * - [`eq()`](http://api.jquery.com/eq/)
2680 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2681 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2682 * - [`html()`](http://api.jquery.com/html/)
2683 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2684 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2685 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2686 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2687 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2688 * - [`prepend()`](http://api.jquery.com/prepend/)
2689 * - [`prop()`](http://api.jquery.com/prop/)
2690 * - [`ready()`](http://api.jquery.com/ready/)
2691 * - [`remove()`](http://api.jquery.com/remove/)
2692 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2693 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2694 * - [`removeData()`](http://api.jquery.com/removeData/)
2695 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2696 * - [`text()`](http://api.jquery.com/text/)
2697 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2698 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2699 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2700 * - [`val()`](http://api.jquery.com/val/)
2701 * - [`wrap()`](http://api.jquery.com/wrap/)
2703 * ## jQuery/jqLite Extras
2704 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2707 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2708 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2709 * element before it is removed.
2712 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2713 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2714 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2716 * - `injector()` - retrieves the injector of the current element or its parent.
2717 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2718 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2720 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2721 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2722 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2723 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2724 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2725 * parent element is reached.
2727 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2728 * @returns {Object} jQuery object.
2731 JQLite.expando = 'ng339';
2733 var jqCache = JQLite.cache = {},
2735 addEventListenerFn = function(element, type, fn) {
2736 element.addEventListener(type, fn, false);
2738 removeEventListenerFn = function(element, type, fn) {
2739 element.removeEventListener(type, fn, false);
2743 * !!! This is an undocumented "private" function !!!
2745 JQLite._data = function(node) {
2746 //jQuery always returns an object on cache miss
2747 return this.cache[node[this.expando]] || {};
2750 function jqNextId() { return ++jqId; }
2753 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2754 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2755 var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2756 var jqLiteMinErr = minErr('jqLite');
2759 * Converts snake_case to camelCase.
2760 * Also there is special case for Moz prefix starting with upper case letter.
2761 * @param name Name to normalize
2763 function camelCase(name) {
2765 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2766 return offset ? letter.toUpperCase() : letter;
2768 replace(MOZ_HACK_REGEXP, 'Moz$1');
2771 var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2772 var HTML_REGEXP = /<|&#?\w+;/;
2773 var TAG_NAME_REGEXP = /<([\w:-]+)/;
2774 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2777 'option': [1, '<select multiple="multiple">', '</select>'],
2779 'thead': [1, '<table>', '</table>'],
2780 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2781 'tr': [2, '<table><tbody>', '</tbody></table>'],
2782 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2783 '_default': [0, "", ""]
2786 wrapMap.optgroup = wrapMap.option;
2787 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2788 wrapMap.th = wrapMap.td;
2791 function jqLiteIsTextNode(html) {
2792 return !HTML_REGEXP.test(html);
2795 function jqLiteAcceptsData(node) {
2796 // The window object can accept data but has no nodeType
2797 // Otherwise we are only interested in elements (1) and documents (9)
2798 var nodeType = node.nodeType;
2799 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2802 function jqLiteHasData(node) {
2803 for (var key in jqCache[node.ng339]) {
2809 function jqLiteBuildFragment(html, context) {
2811 fragment = context.createDocumentFragment(),
2814 if (jqLiteIsTextNode(html)) {
2815 // Convert non-html into a text node
2816 nodes.push(context.createTextNode(html));
2818 // Convert html into DOM nodes
2819 tmp = tmp || fragment.appendChild(context.createElement("div"));
2820 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2821 wrap = wrapMap[tag] || wrapMap._default;
2822 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2824 // Descend through wrappers to the right content
2827 tmp = tmp.lastChild;
2830 nodes = concat(nodes, tmp.childNodes);
2832 tmp = fragment.firstChild;
2833 tmp.textContent = "";
2836 // Remove wrapper from fragment
2837 fragment.textContent = "";
2838 fragment.innerHTML = ""; // Clear inner HTML
2839 forEach(nodes, function(node) {
2840 fragment.appendChild(node);
2846 function jqLiteParseHTML(html, context) {
2847 context = context || document;
2850 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2851 return [context.createElement(parsed[1])];
2854 if ((parsed = jqLiteBuildFragment(html, context))) {
2855 return parsed.childNodes;
2862 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2863 var jqLiteContains = Node.prototype.contains || function(arg) {
2864 // jshint bitwise: false
2865 return !!(this.compareDocumentPosition(arg) & 16);
2866 // jshint bitwise: true
2869 /////////////////////////////////////////////
2870 function JQLite(element) {
2871 if (element instanceof JQLite) {
2877 if (isString(element)) {
2878 element = trim(element);
2881 if (!(this instanceof JQLite)) {
2882 if (argIsString && element.charAt(0) != '<') {
2883 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2885 return new JQLite(element);
2889 jqLiteAddNodes(this, jqLiteParseHTML(element));
2891 jqLiteAddNodes(this, element);
2895 function jqLiteClone(element) {
2896 return element.cloneNode(true);
2899 function jqLiteDealoc(element, onlyDescendants) {
2900 if (!onlyDescendants) jqLiteRemoveData(element);
2902 if (element.querySelectorAll) {
2903 var descendants = element.querySelectorAll('*');
2904 for (var i = 0, l = descendants.length; i < l; i++) {
2905 jqLiteRemoveData(descendants[i]);
2910 function jqLiteOff(element, type, fn, unsupported) {
2911 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2913 var expandoStore = jqLiteExpandoStore(element);
2914 var events = expandoStore && expandoStore.events;
2915 var handle = expandoStore && expandoStore.handle;
2917 if (!handle) return; //no listeners registered
2920 for (type in events) {
2921 if (type !== '$destroy') {
2922 removeEventListenerFn(element, type, handle);
2924 delete events[type];
2928 var removeHandler = function(type) {
2929 var listenerFns = events[type];
2930 if (isDefined(fn)) {
2931 arrayRemove(listenerFns || [], fn);
2933 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2934 removeEventListenerFn(element, type, handle);
2935 delete events[type];
2939 forEach(type.split(' '), function(type) {
2940 removeHandler(type);
2941 if (MOUSE_EVENT_MAP[type]) {
2942 removeHandler(MOUSE_EVENT_MAP[type]);
2948 function jqLiteRemoveData(element, name) {
2949 var expandoId = element.ng339;
2950 var expandoStore = expandoId && jqCache[expandoId];
2954 delete expandoStore.data[name];
2958 if (expandoStore.handle) {
2959 if (expandoStore.events.$destroy) {
2960 expandoStore.handle({}, '$destroy');
2964 delete jqCache[expandoId];
2965 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2970 function jqLiteExpandoStore(element, createIfNecessary) {
2971 var expandoId = element.ng339,
2972 expandoStore = expandoId && jqCache[expandoId];
2974 if (createIfNecessary && !expandoStore) {
2975 element.ng339 = expandoId = jqNextId();
2976 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2979 return expandoStore;
2983 function jqLiteData(element, key, value) {
2984 if (jqLiteAcceptsData(element)) {
2986 var isSimpleSetter = isDefined(value);
2987 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2988 var massGetter = !key;
2989 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2990 var data = expandoStore && expandoStore.data;
2992 if (isSimpleSetter) { // data('key', value)
2995 if (massGetter) { // data()
2998 if (isSimpleGetter) { // data('key')
2999 // don't force creation of expandoStore if it doesn't exist yet
3000 return data && data[key];
3001 } else { // mass-setter: data({key1: val1, key2: val2})
3009 function jqLiteHasClass(element, selector) {
3010 if (!element.getAttribute) return false;
3011 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
3012 indexOf(" " + selector + " ") > -1);
3015 function jqLiteRemoveClass(element, cssClasses) {
3016 if (cssClasses && element.setAttribute) {
3017 forEach(cssClasses.split(' '), function(cssClass) {
3018 element.setAttribute('class', trim(
3019 (" " + (element.getAttribute('class') || '') + " ")
3020 .replace(/[\n\t]/g, " ")
3021 .replace(" " + trim(cssClass) + " ", " "))
3027 function jqLiteAddClass(element, cssClasses) {
3028 if (cssClasses && element.setAttribute) {
3029 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3030 .replace(/[\n\t]/g, " ");
3032 forEach(cssClasses.split(' '), function(cssClass) {
3033 cssClass = trim(cssClass);
3034 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3035 existingClasses += cssClass + ' ';
3039 element.setAttribute('class', trim(existingClasses));
3044 function jqLiteAddNodes(root, elements) {
3045 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3049 // if a Node (the most common case)
3050 if (elements.nodeType) {
3051 root[root.length++] = elements;
3053 var length = elements.length;
3055 // if an Array or NodeList and not a Window
3056 if (typeof length === 'number' && elements.window !== elements) {
3058 for (var i = 0; i < length; i++) {
3059 root[root.length++] = elements[i];
3063 root[root.length++] = elements;
3070 function jqLiteController(element, name) {
3071 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3074 function jqLiteInheritedData(element, name, value) {
3075 // if element is the document object work with the html element instead
3076 // this makes $(document).scope() possible
3077 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3078 element = element.documentElement;
3080 var names = isArray(name) ? name : [name];
3083 for (var i = 0, ii = names.length; i < ii; i++) {
3084 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3087 // If dealing with a document fragment node with a host element, and no parent, use the host
3088 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3089 // to lookup parent controllers.
3090 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3094 function jqLiteEmpty(element) {
3095 jqLiteDealoc(element, true);
3096 while (element.firstChild) {
3097 element.removeChild(element.firstChild);
3101 function jqLiteRemove(element, keepData) {
3102 if (!keepData) jqLiteDealoc(element);
3103 var parent = element.parentNode;
3104 if (parent) parent.removeChild(element);
3108 function jqLiteDocumentLoaded(action, win) {
3109 win = win || window;
3110 if (win.document.readyState === 'complete') {
3111 // Force the action to be run async for consistent behaviour
3112 // from the action's point of view
3113 // i.e. it will definitely not be in a $apply
3114 win.setTimeout(action);
3116 // No need to unbind this handler as load is only ever called once
3117 jqLite(win).on('load', action);
3121 //////////////////////////////////////////
3122 // Functions which are declared directly.
3123 //////////////////////////////////////////
3124 var JQLitePrototype = JQLite.prototype = {
3125 ready: function(fn) {
3128 function trigger() {
3134 // check if document is already loaded
3135 if (document.readyState === 'complete') {
3136 setTimeout(trigger);
3138 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3139 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3141 JQLite(window).on('load', trigger); // fallback to window.onload for others
3145 toString: function() {
3147 forEach(this, function(e) { value.push('' + e);});
3148 return '[' + value.join(', ') + ']';
3151 eq: function(index) {
3152 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3161 //////////////////////////////////////////
3162 // Functions iterating getter/setters.
3163 // these functions return self on setter and
3165 //////////////////////////////////////////
3166 var BOOLEAN_ATTR = {};
3167 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3168 BOOLEAN_ATTR[lowercase(value)] = value;
3170 var BOOLEAN_ELEMENTS = {};
3171 forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3172 BOOLEAN_ELEMENTS[value] = true;
3174 var ALIASED_ATTR = {
3175 'ngMinlength': 'minlength',
3176 'ngMaxlength': 'maxlength',
3179 'ngPattern': 'pattern'
3182 function getBooleanAttrName(element, name) {
3183 // check dom last since we will most likely fail on name
3184 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3186 // booleanAttr is here twice to minimize DOM access
3187 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3190 function getAliasedAttrName(name) {
3191 return ALIASED_ATTR[name];
3196 removeData: jqLiteRemoveData,
3197 hasData: jqLiteHasData
3198 }, function(fn, name) {
3204 inheritedData: jqLiteInheritedData,
3206 scope: function(element) {
3207 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3208 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3211 isolateScope: function(element) {
3212 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3213 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3216 controller: jqLiteController,
3218 injector: function(element) {
3219 return jqLiteInheritedData(element, '$injector');
3222 removeAttr: function(element, name) {
3223 element.removeAttribute(name);
3226 hasClass: jqLiteHasClass,
3228 css: function(element, name, value) {
3229 name = camelCase(name);
3231 if (isDefined(value)) {
3232 element.style[name] = value;
3234 return element.style[name];
3238 attr: function(element, name, value) {
3239 var nodeType = element.nodeType;
3240 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3243 var lowercasedName = lowercase(name);
3244 if (BOOLEAN_ATTR[lowercasedName]) {
3245 if (isDefined(value)) {
3247 element[name] = true;
3248 element.setAttribute(name, lowercasedName);
3250 element[name] = false;
3251 element.removeAttribute(lowercasedName);
3254 return (element[name] ||
3255 (element.attributes.getNamedItem(name) || noop).specified)
3259 } else if (isDefined(value)) {
3260 element.setAttribute(name, value);
3261 } else if (element.getAttribute) {
3262 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3263 // some elements (e.g. Document) don't have get attribute, so return undefined
3264 var ret = element.getAttribute(name, 2);
3265 // normalize non-existing attributes to undefined (as jQuery)
3266 return ret === null ? undefined : ret;
3270 prop: function(element, name, value) {
3271 if (isDefined(value)) {
3272 element[name] = value;
3274 return element[name];
3282 function getText(element, value) {
3283 if (isUndefined(value)) {
3284 var nodeType = element.nodeType;
3285 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3287 element.textContent = value;
3291 val: function(element, value) {
3292 if (isUndefined(value)) {
3293 if (element.multiple && nodeName_(element) === 'select') {
3295 forEach(element.options, function(option) {
3296 if (option.selected) {
3297 result.push(option.value || option.text);
3300 return result.length === 0 ? null : result;
3302 return element.value;
3304 element.value = value;
3307 html: function(element, value) {
3308 if (isUndefined(value)) {
3309 return element.innerHTML;
3311 jqLiteDealoc(element, true);
3312 element.innerHTML = value;
3316 }, function(fn, name) {
3318 * Properties: writes return selection, reads return first value
3320 JQLite.prototype[name] = function(arg1, arg2) {
3322 var nodeCount = this.length;
3324 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3325 // in a way that survives minification.
3326 // jqLiteEmpty takes no arguments but is a setter.
3327 if (fn !== jqLiteEmpty &&
3328 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3329 if (isObject(arg1)) {
3331 // we are a write, but the object properties are the key/values
3332 for (i = 0; i < nodeCount; i++) {
3333 if (fn === jqLiteData) {
3334 // data() takes the whole object in jQuery
3338 fn(this[i], key, arg1[key]);
3342 // return self for chaining
3345 // we are a read, so read the first child.
3346 // TODO: do we still need this?
3348 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3349 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3350 for (var j = 0; j < jj; j++) {
3351 var nodeValue = fn(this[j], arg1, arg2);
3352 value = value ? value + nodeValue : nodeValue;
3357 // we are a write, so apply to all children
3358 for (i = 0; i < nodeCount; i++) {
3359 fn(this[i], arg1, arg2);
3361 // return self for chaining
3367 function createEventHandler(element, events) {
3368 var eventHandler = function(event, type) {
3369 // jQuery specific api
3370 event.isDefaultPrevented = function() {
3371 return event.defaultPrevented;
3374 var eventFns = events[type || event.type];
3375 var eventFnsLength = eventFns ? eventFns.length : 0;
3377 if (!eventFnsLength) return;
3379 if (isUndefined(event.immediatePropagationStopped)) {
3380 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3381 event.stopImmediatePropagation = function() {
3382 event.immediatePropagationStopped = true;
3384 if (event.stopPropagation) {
3385 event.stopPropagation();
3388 if (originalStopImmediatePropagation) {
3389 originalStopImmediatePropagation.call(event);
3394 event.isImmediatePropagationStopped = function() {
3395 return event.immediatePropagationStopped === true;
3398 // Some events have special handlers that wrap the real handler
3399 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3401 // Copy event handlers in case event handlers array is modified during execution.
3402 if ((eventFnsLength > 1)) {
3403 eventFns = shallowCopy(eventFns);
3406 for (var i = 0; i < eventFnsLength; i++) {
3407 if (!event.isImmediatePropagationStopped()) {
3408 handlerWrapper(element, event, eventFns[i]);
3413 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3414 // events on `element`
3415 eventHandler.elem = element;
3416 return eventHandler;
3419 function defaultHandlerWrapper(element, event, handler) {
3420 handler.call(element, event);
3423 function specialMouseHandlerWrapper(target, event, handler) {
3424 // Refer to jQuery's implementation of mouseenter & mouseleave
3425 // Read about mouseenter and mouseleave:
3426 // http://www.quirksmode.org/js/events_mouse.html#link8
3427 var related = event.relatedTarget;
3428 // For mousenter/leave call the handler if related is outside the target.
3429 // NB: No relatedTarget if the mouse left/entered the browser window
3430 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3431 handler.call(target, event);
3435 //////////////////////////////////////////
3436 // Functions iterating traversal.
3437 // These functions chain results into a single
3439 //////////////////////////////////////////
3441 removeData: jqLiteRemoveData,
3443 on: function jqLiteOn(element, type, fn, unsupported) {
3444 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3446 // Do not add event handlers to non-elements because they will not be cleaned up.
3447 if (!jqLiteAcceptsData(element)) {
3451 var expandoStore = jqLiteExpandoStore(element, true);
3452 var events = expandoStore.events;
3453 var handle = expandoStore.handle;
3456 handle = expandoStore.handle = createEventHandler(element, events);
3459 // http://jsperf.com/string-indexof-vs-split
3460 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3461 var i = types.length;
3463 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3464 var eventFns = events[type];
3467 eventFns = events[type] = [];
3468 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3469 if (type !== '$destroy' && !noEventListener) {
3470 addEventListenerFn(element, type, handle);
3479 if (MOUSE_EVENT_MAP[type]) {
3480 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3481 addHandler(type, undefined, true);
3490 one: function(element, type, fn) {
3491 element = jqLite(element);
3493 //add the listener twice so that when it is called
3494 //you can remove the original function and still be
3495 //able to call element.off(ev, fn) normally
3496 element.on(type, function onFn() {
3497 element.off(type, fn);
3498 element.off(type, onFn);
3500 element.on(type, fn);
3503 replaceWith: function(element, replaceNode) {
3504 var index, parent = element.parentNode;
3505 jqLiteDealoc(element);
3506 forEach(new JQLite(replaceNode), function(node) {
3508 parent.insertBefore(node, index.nextSibling);
3510 parent.replaceChild(node, element);
3516 children: function(element) {
3518 forEach(element.childNodes, function(element) {
3519 if (element.nodeType === NODE_TYPE_ELEMENT) {
3520 children.push(element);
3526 contents: function(element) {
3527 return element.contentDocument || element.childNodes || [];
3530 append: function(element, node) {
3531 var nodeType = element.nodeType;
3532 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3534 node = new JQLite(node);
3536 for (var i = 0, ii = node.length; i < ii; i++) {
3537 var child = node[i];
3538 element.appendChild(child);
3542 prepend: function(element, node) {
3543 if (element.nodeType === NODE_TYPE_ELEMENT) {
3544 var index = element.firstChild;
3545 forEach(new JQLite(node), function(child) {
3546 element.insertBefore(child, index);
3551 wrap: function(element, wrapNode) {
3552 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3553 var parent = element.parentNode;
3555 parent.replaceChild(wrapNode, element);
3557 wrapNode.appendChild(element);
3560 remove: jqLiteRemove,
3562 detach: function(element) {
3563 jqLiteRemove(element, true);
3566 after: function(element, newElement) {
3567 var index = element, parent = element.parentNode;
3568 newElement = new JQLite(newElement);
3570 for (var i = 0, ii = newElement.length; i < ii; i++) {
3571 var node = newElement[i];
3572 parent.insertBefore(node, index.nextSibling);
3577 addClass: jqLiteAddClass,
3578 removeClass: jqLiteRemoveClass,
3580 toggleClass: function(element, selector, condition) {
3582 forEach(selector.split(' '), function(className) {
3583 var classCondition = condition;
3584 if (isUndefined(classCondition)) {
3585 classCondition = !jqLiteHasClass(element, className);
3587 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3592 parent: function(element) {
3593 var parent = element.parentNode;
3594 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3597 next: function(element) {
3598 return element.nextElementSibling;
3601 find: function(element, selector) {
3602 if (element.getElementsByTagName) {
3603 return element.getElementsByTagName(selector);
3611 triggerHandler: function(element, event, extraParameters) {
3613 var dummyEvent, eventFnsCopy, handlerArgs;
3614 var eventName = event.type || event;
3615 var expandoStore = jqLiteExpandoStore(element);
3616 var events = expandoStore && expandoStore.events;
3617 var eventFns = events && events[eventName];
3620 // Create a dummy event to pass to the handlers
3622 preventDefault: function() { this.defaultPrevented = true; },
3623 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3624 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3625 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3626 stopPropagation: noop,
3631 // If a custom event was provided then extend our dummy event with it
3633 dummyEvent = extend(dummyEvent, event);
3636 // Copy event handlers in case event handlers array is modified during execution.
3637 eventFnsCopy = shallowCopy(eventFns);
3638 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3640 forEach(eventFnsCopy, function(fn) {
3641 if (!dummyEvent.isImmediatePropagationStopped()) {
3642 fn.apply(element, handlerArgs);
3647 }, function(fn, name) {
3649 * chaining functions
3651 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3654 for (var i = 0, ii = this.length; i < ii; i++) {
3655 if (isUndefined(value)) {
3656 value = fn(this[i], arg1, arg2, arg3);
3657 if (isDefined(value)) {
3658 // any function which returns a value needs to be wrapped
3659 value = jqLite(value);
3662 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3665 return isDefined(value) ? value : this;
3668 // bind legacy bind/unbind to on/off
3669 JQLite.prototype.bind = JQLite.prototype.on;
3670 JQLite.prototype.unbind = JQLite.prototype.off;
3674 // Provider for private $$jqLite service
3675 function $$jqLiteProvider() {
3676 this.$get = function $$jqLite() {
3677 return extend(JQLite, {
3678 hasClass: function(node, classes) {
3679 if (node.attr) node = node[0];
3680 return jqLiteHasClass(node, classes);
3682 addClass: function(node, classes) {
3683 if (node.attr) node = node[0];
3684 return jqLiteAddClass(node, classes);
3686 removeClass: function(node, classes) {
3687 if (node.attr) node = node[0];
3688 return jqLiteRemoveClass(node, classes);
3695 * Computes a hash of an 'obj'.
3698 * number is number as string
3699 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3700 * that is also assigned to the $$hashKey property of the object.
3703 * @returns {string} hash string such that the same input will have the same hash string.
3704 * The resulting string key is in 'type:hashKey' format.
3706 function hashKey(obj, nextUidFn) {
3707 var key = obj && obj.$$hashKey;
3710 if (typeof key === 'function') {
3711 key = obj.$$hashKey();
3716 var objType = typeof obj;
3717 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3718 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3720 key = objType + ':' + obj;
3727 * HashMap which can use objects as keys
3729 function HashMap(array, isolatedUid) {
3732 this.nextUid = function() {
3736 forEach(array, this.put, this);
3738 HashMap.prototype = {
3740 * Store key value pair
3741 * @param key key to store can be any type
3742 * @param value value to store can be any type
3744 put: function(key, value) {
3745 this[hashKey(key, this.nextUid)] = value;
3750 * @returns {Object} the value for the key
3752 get: function(key) {
3753 return this[hashKey(key, this.nextUid)];
3757 * Remove the key/value pair
3760 remove: function(key) {
3761 var value = this[key = hashKey(key, this.nextUid)];
3767 var $$HashMapProvider = [function() {
3768 this.$get = [function() {
3776 * @name angular.injector
3780 * Creates an injector object that can be used for retrieving services as well as for
3781 * dependency injection (see {@link guide/di dependency injection}).
3783 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3784 * {@link angular.module}. The `ng` module must be explicitly added.
3785 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3786 * disallows argument name annotation inference.
3787 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3792 * // create an injector
3793 * var $injector = angular.injector(['ng']);
3795 * // use the injector to kick off your application
3796 * // use the type inference to auto inject arguments, or use implicit injection
3797 * $injector.invoke(function($rootScope, $compile, $document) {
3798 * $compile($document)($rootScope);
3799 * $rootScope.$digest();
3803 * Sometimes you want to get access to the injector of a currently running Angular app
3804 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3805 * application has been bootstrapped. You can do this using the extra `injector()` added
3806 * to JQuery/jqLite elements. See {@link angular.element}.
3808 * *This is fairly rare but could be the case if a third party library is injecting the
3811 * In the following example a new block of HTML containing a `ng-controller`
3812 * directive is added to the end of the document body by JQuery. We then compile and link
3813 * it into the current AngularJS scope.
3816 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3817 * $(document.body).append($div);
3819 * angular.element(document).injector().invoke(function($compile) {
3820 * var scope = angular.element($div).scope();
3821 * $compile($div)(scope);
3832 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3835 var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3836 var FN_ARG_SPLIT = /,/;
3837 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3838 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3839 var $injectorMinErr = minErr('$injector');
3841 function anonFn(fn) {
3842 // For anonymous functions, showing at the very least the function signature can help in
3844 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3845 args = fnText.match(FN_ARGS);
3847 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3852 function annotate(fn, strictDi, name) {
3858 if (typeof fn === 'function') {
3859 if (!($inject = fn.$inject)) {
3863 if (!isString(name) || !name) {
3864 name = fn.name || anonFn(fn);
3866 throw $injectorMinErr('strictdi',
3867 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3869 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3870 argDecl = fnText.match(FN_ARGS);
3871 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3872 arg.replace(FN_ARG, function(all, underscore, name) {
3877 fn.$inject = $inject;
3879 } else if (isArray(fn)) {
3880 last = fn.length - 1;
3881 assertArgFn(fn[last], 'fn');
3882 $inject = fn.slice(0, last);
3884 assertArgFn(fn, 'fn', true);
3889 ///////////////////////////////////////
3897 * `$injector` is used to retrieve object instances as defined by
3898 * {@link auto.$provide provider}, instantiate types, invoke methods,
3901 * The following always holds true:
3904 * var $injector = angular.injector();
3905 * expect($injector.get('$injector')).toBe($injector);
3906 * expect($injector.invoke(function($injector) {
3908 * })).toBe($injector);
3911 * # Injection Function Annotation
3913 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3914 * following are all valid ways of annotating function with injection arguments and are equivalent.
3917 * // inferred (only works if code not minified/obfuscated)
3918 * $injector.invoke(function(serviceA){});
3921 * function explicit(serviceA) {};
3922 * explicit.$inject = ['serviceA'];
3923 * $injector.invoke(explicit);
3926 * $injector.invoke(['serviceA', function(serviceA){}]);
3931 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3932 * can then be parsed and the function arguments can be extracted. This method of discovering
3933 * annotations is disallowed when the injector is in strict mode.
3934 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3937 * ## `$inject` Annotation
3938 * By adding an `$inject` property onto a function the injection parameters can be specified.
3941 * As an array of injection names, where the last item in the array is the function to call.
3946 * @name $injector#get
3949 * Return an instance of the service.
3951 * @param {string} name The name of the instance to retrieve.
3952 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3953 * @return {*} The instance.
3958 * @name $injector#invoke
3961 * Invoke the method and supply the method arguments from the `$injector`.
3963 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3964 * injected according to the {@link guide/di $inject Annotation} rules.
3965 * @param {Object=} self The `this` for the invoked method.
3966 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3967 * object first, before the `$injector` is consulted.
3968 * @returns {*} the value returned by the invoked `fn` function.
3973 * @name $injector#has
3976 * Allows the user to query if the particular service exists.
3978 * @param {string} name Name of the service to query.
3979 * @returns {boolean} `true` if injector has given service.
3984 * @name $injector#instantiate
3986 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3987 * operator, and supplies all of the arguments to the constructor function as specified by the
3988 * constructor annotation.
3990 * @param {Function} Type Annotated constructor function.
3991 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3992 * object first, before the `$injector` is consulted.
3993 * @returns {Object} new instance of `Type`.
3998 * @name $injector#annotate
4001 * Returns an array of service names which the function is requesting for injection. This API is
4002 * used by the injector to determine which services need to be injected into the function when the
4003 * function is invoked. There are three ways in which the function can be annotated with the needed
4008 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4009 * by converting the function into a string using `toString()` method and extracting the argument
4013 * function MyController($scope, $route) {
4018 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4021 * You can disallow this method by using strict injection mode.
4023 * This method does not work with code minification / obfuscation. For this reason the following
4024 * annotation strategies are supported.
4026 * # The `$inject` property
4028 * If a function has an `$inject` property and its value is an array of strings, then the strings
4029 * represent names of services to be injected into the function.
4032 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4035 * // Define function dependencies
4036 * MyController['$inject'] = ['$scope', '$route'];
4039 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4042 * # The array notation
4044 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4045 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4046 * a way that survives minification is a better choice:
4049 * // We wish to write this (not minification / obfuscation safe)
4050 * injector.invoke(function($compile, $rootScope) {
4054 * // We are forced to write break inlining
4055 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4058 * tmpFn.$inject = ['$compile', '$rootScope'];
4059 * injector.invoke(tmpFn);
4061 * // To better support inline function the inline annotation is supported
4062 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4067 * expect(injector.annotate(
4068 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4069 * ).toEqual(['$compile', '$rootScope']);
4072 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4073 * be retrieved as described above.
4075 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4077 * @returns {Array.<string>} The names of the services which the function requires.
4089 * The {@link auto.$provide $provide} service has a number of methods for registering components
4090 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4091 * {@link angular.Module}.
4093 * An Angular **service** is a singleton object created by a **service factory**. These **service
4094 * factories** are functions which, in turn, are created by a **service provider**.
4095 * The **service providers** are constructor functions. When instantiated they must contain a
4096 * property called `$get`, which holds the **service factory** function.
4098 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4099 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4100 * function to get the instance of the **service**.
4102 * Often services have no configuration options and there is no need to add methods to the service
4103 * provider. The provider will be no more than a constructor function with a `$get` property. For
4104 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4105 * services without specifying a provider.
4107 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4108 * {@link auto.$injector $injector}
4109 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4110 * providers and services.
4111 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4112 * services, not providers.
4113 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4114 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4115 * given factory function.
4116 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4117 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4118 * a new object using the given constructor function.
4120 * See the individual methods for more information and examples.
4125 * @name $provide#provider
4128 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4129 * are constructor functions, whose instances are responsible for "providing" a factory for a
4132 * Service provider names start with the name of the service they provide followed by `Provider`.
4133 * For example, the {@link ng.$log $log} service has a provider called
4134 * {@link ng.$logProvider $logProvider}.
4136 * Service provider objects can have additional methods which allow configuration of the provider
4137 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4138 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4139 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4140 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4143 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4145 * @param {(Object|function())} provider If the provider is:
4147 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4148 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4149 * - `Constructor`: a new instance of the provider will be created using
4150 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4152 * @returns {Object} registered provider instance
4156 * The following example shows how to create a simple event tracking service and register it using
4157 * {@link auto.$provide#provider $provide.provider()}.
4160 * // Define the eventTracker provider
4161 * function EventTrackerProvider() {
4162 * var trackingUrl = '/track';
4164 * // A provider method for configuring where the tracked events should been saved
4165 * this.setTrackingUrl = function(url) {
4166 * trackingUrl = url;
4169 * // The service factory function
4170 * this.$get = ['$http', function($http) {
4171 * var trackedEvents = {};
4173 * // Call this to track an event
4174 * event: function(event) {
4175 * var count = trackedEvents[event] || 0;
4177 * trackedEvents[event] = count;
4180 * // Call this to save the tracked events to the trackingUrl
4181 * save: function() {
4182 * $http.post(trackingUrl, trackedEvents);
4188 * describe('eventTracker', function() {
4191 * beforeEach(module(function($provide) {
4192 * // Register the eventTracker provider
4193 * $provide.provider('eventTracker', EventTrackerProvider);
4196 * beforeEach(module(function(eventTrackerProvider) {
4197 * // Configure eventTracker provider
4198 * eventTrackerProvider.setTrackingUrl('/custom-track');
4201 * it('tracks events', inject(function(eventTracker) {
4202 * expect(eventTracker.event('login')).toEqual(1);
4203 * expect(eventTracker.event('login')).toEqual(2);
4206 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4207 * postSpy = spyOn($http, 'post');
4208 * eventTracker.event('login');
4209 * eventTracker.save();
4210 * expect(postSpy).toHaveBeenCalled();
4211 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4212 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4213 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4221 * @name $provide#factory
4224 * Register a **service factory**, which will be called to return the service instance.
4225 * This is short for registering a service where its provider consists of only a `$get` property,
4226 * which is the given service factory function.
4227 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4228 * configure your service in a provider.
4230 * @param {string} name The name of the instance.
4231 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4232 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4233 * @returns {Object} registered provider instance
4236 * Here is an example of registering a service
4238 * $provide.factory('ping', ['$http', function($http) {
4239 * return function ping() {
4240 * return $http.send('/ping');
4244 * You would then inject and use this service like this:
4246 * someModule.controller('Ctrl', ['ping', function(ping) {
4255 * @name $provide#service
4258 * Register a **service constructor**, which will be invoked with `new` to create the service
4260 * This is short for registering a service where its provider's `$get` property is the service
4261 * constructor function that will be used to instantiate the service instance.
4263 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4266 * @param {string} name The name of the instance.
4267 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4268 * that will be instantiated.
4269 * @returns {Object} registered provider instance
4272 * Here is an example of registering a service using
4273 * {@link auto.$provide#service $provide.service(class)}.
4275 * var Ping = function($http) {
4276 * this.$http = $http;
4279 * Ping.$inject = ['$http'];
4281 * Ping.prototype.send = function() {
4282 * return this.$http.get('/ping');
4284 * $provide.service('ping', Ping);
4286 * You would then inject and use this service like this:
4288 * someModule.controller('Ctrl', ['ping', function(ping) {
4297 * @name $provide#value
4300 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4301 * number, an array, an object or a function. This is short for registering a service where its
4302 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4305 * Value services are similar to constant services, except that they cannot be injected into a
4306 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4308 * {@link auto.$provide#decorator decorator}.
4310 * @param {string} name The name of the instance.
4311 * @param {*} value The value.
4312 * @returns {Object} registered provider instance
4315 * Here are some examples of creating value services.
4317 * $provide.value('ADMIN_USER', 'admin');
4319 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4321 * $provide.value('halfOf', function(value) {
4330 * @name $provide#constant
4333 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4334 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4335 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4336 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4338 * @param {string} name The name of the constant.
4339 * @param {*} value The constant value.
4340 * @returns {Object} registered instance
4343 * Here a some examples of creating constants:
4345 * $provide.constant('SHARD_HEIGHT', 306);
4347 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4349 * $provide.constant('double', function(value) {
4358 * @name $provide#decorator
4361 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4362 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4363 * service. The object returned by the decorator may be the original service, or a new service
4364 * object which replaces or wraps and delegates to the original service.
4366 * @param {string} name The name of the service to decorate.
4367 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4368 * instantiated and should return the decorated service instance. The function is called using
4369 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4370 * Local injection arguments:
4372 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4373 * decorated or delegated to.
4376 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4377 * calls to {@link ng.$log#error $log.warn()}.
4379 * $provide.decorator('$log', ['$delegate', function($delegate) {
4380 * $delegate.warn = $delegate.error;
4387 function createInjector(modulesToLoad, strictDi) {
4388 strictDi = (strictDi === true);
4389 var INSTANTIATING = {},
4390 providerSuffix = 'Provider',
4392 loadedModules = new HashMap([], true),
4395 provider: supportObject(provider),
4396 factory: supportObject(factory),
4397 service: supportObject(service),
4398 value: supportObject(value),
4399 constant: supportObject(constant),
4400 decorator: decorator
4403 providerInjector = (providerCache.$injector =
4404 createInternalInjector(providerCache, function(serviceName, caller) {
4405 if (angular.isString(caller)) {
4408 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4411 instanceInjector = (instanceCache.$injector =
4412 createInternalInjector(instanceCache, function(serviceName, caller) {
4413 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4414 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4418 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4420 return instanceInjector;
4422 ////////////////////////////////////
4424 ////////////////////////////////////
4426 function supportObject(delegate) {
4427 return function(key, value) {
4428 if (isObject(key)) {
4429 forEach(key, reverseParams(delegate));
4431 return delegate(key, value);
4436 function provider(name, provider_) {
4437 assertNotHasOwnProperty(name, 'service');
4438 if (isFunction(provider_) || isArray(provider_)) {
4439 provider_ = providerInjector.instantiate(provider_);
4441 if (!provider_.$get) {
4442 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4444 return providerCache[name + providerSuffix] = provider_;
4447 function enforceReturnValue(name, factory) {
4448 return function enforcedReturnValue() {
4449 var result = instanceInjector.invoke(factory, this);
4450 if (isUndefined(result)) {
4451 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4457 function factory(name, factoryFn, enforce) {
4458 return provider(name, {
4459 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4463 function service(name, constructor) {
4464 return factory(name, ['$injector', function($injector) {
4465 return $injector.instantiate(constructor);
4469 function value(name, val) { return factory(name, valueFn(val), false); }
4471 function constant(name, value) {
4472 assertNotHasOwnProperty(name, 'constant');
4473 providerCache[name] = value;
4474 instanceCache[name] = value;
4477 function decorator(serviceName, decorFn) {
4478 var origProvider = providerInjector.get(serviceName + providerSuffix),
4479 orig$get = origProvider.$get;
4481 origProvider.$get = function() {
4482 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4483 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4487 ////////////////////////////////////
4489 ////////////////////////////////////
4490 function loadModules(modulesToLoad) {
4491 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4492 var runBlocks = [], moduleFn;
4493 forEach(modulesToLoad, function(module) {
4494 if (loadedModules.get(module)) return;
4495 loadedModules.put(module, true);
4497 function runInvokeQueue(queue) {
4499 for (i = 0, ii = queue.length; i < ii; i++) {
4500 var invokeArgs = queue[i],
4501 provider = providerInjector.get(invokeArgs[0]);
4503 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4508 if (isString(module)) {
4509 moduleFn = angularModule(module);
4510 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4511 runInvokeQueue(moduleFn._invokeQueue);
4512 runInvokeQueue(moduleFn._configBlocks);
4513 } else if (isFunction(module)) {
4514 runBlocks.push(providerInjector.invoke(module));
4515 } else if (isArray(module)) {
4516 runBlocks.push(providerInjector.invoke(module));
4518 assertArgFn(module, 'module');
4521 if (isArray(module)) {
4522 module = module[module.length - 1];
4524 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4525 // Safari & FF's stack traces don't contain error.message content
4526 // unlike those of Chrome and IE
4527 // So if stack doesn't contain message, we create a new string that contains both.
4528 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4530 e = e.message + '\n' + e.stack;
4532 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4533 module, e.stack || e.message || e);
4539 ////////////////////////////////////
4540 // internal Injector
4541 ////////////////////////////////////
4543 function createInternalInjector(cache, factory) {
4545 function getService(serviceName, caller) {
4546 if (cache.hasOwnProperty(serviceName)) {
4547 if (cache[serviceName] === INSTANTIATING) {
4548 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4549 serviceName + ' <- ' + path.join(' <- '));
4551 return cache[serviceName];
4554 path.unshift(serviceName);
4555 cache[serviceName] = INSTANTIATING;
4556 return cache[serviceName] = factory(serviceName, caller);
4558 if (cache[serviceName] === INSTANTIATING) {
4559 delete cache[serviceName];
4568 function invoke(fn, self, locals, serviceName) {
4569 if (typeof locals === 'string') {
4570 serviceName = locals;
4575 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4579 for (i = 0, length = $inject.length; i < length; i++) {
4581 if (typeof key !== 'string') {
4582 throw $injectorMinErr('itkn',
4583 'Incorrect injection token! Expected service name as string, got {0}', key);
4586 locals && locals.hasOwnProperty(key)
4588 : getService(key, serviceName)
4595 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4597 return fn.apply(self, args);
4600 function instantiate(Type, locals, serviceName) {
4601 // Check if Type is annotated and use just the given function at n-1 as parameter
4602 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4603 // Object creation: http://jsperf.com/create-constructor/2
4604 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4605 var returnedValue = invoke(Type, instance, locals, serviceName);
4607 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4612 instantiate: instantiate,
4614 annotate: createInjector.$$annotate,
4615 has: function(name) {
4616 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4622 createInjector.$$annotate = annotate;
4626 * @name $anchorScrollProvider
4629 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4630 * {@link ng.$location#hash $location.hash()} changes.
4632 function $AnchorScrollProvider() {
4634 var autoScrollingEnabled = true;
4638 * @name $anchorScrollProvider#disableAutoScrolling
4641 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4642 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4643 * Use this method to disable automatic scrolling.
4645 * If automatic scrolling is disabled, one must explicitly call
4646 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4649 this.disableAutoScrolling = function() {
4650 autoScrollingEnabled = false;
4655 * @name $anchorScroll
4658 * @requires $location
4659 * @requires $rootScope
4662 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4663 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4665 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4667 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4668 * match any anchor whenever it changes. This can be disabled by calling
4669 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4671 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4672 * vertical scroll-offset (either fixed or dynamic).
4674 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4675 * {@link ng.$location#hash $location.hash()} will be used.
4677 * @property {(number|function|jqLite)} yOffset
4678 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4679 * positioned elements at the top of the page, such as navbars, headers etc.
4681 * `yOffset` can be specified in various ways:
4682 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4683 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4684 * a number representing the offset (in pixels).<br /><br />
4685 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4686 * the top of the page to the element's bottom will be used as offset.<br />
4687 * **Note**: The element will be taken into account only as long as its `position` is set to
4688 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4689 * their height and/or positioning according to the viewport's size.
4692 * <div class="alert alert-warning">
4693 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4694 * not some child element.
4698 <example module="anchorScrollExample">
4699 <file name="index.html">
4700 <div id="scrollArea" ng-controller="ScrollController">
4701 <a ng-click="gotoBottom()">Go to bottom</a>
4702 <a id="bottom"></a> You're at the bottom!
4705 <file name="script.js">
4706 angular.module('anchorScrollExample', [])
4707 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4708 function ($scope, $location, $anchorScroll) {
4709 $scope.gotoBottom = function() {
4710 // set the location.hash to the id of
4711 // the element you wish to scroll to.
4712 $location.hash('bottom');
4714 // call $anchorScroll()
4719 <file name="style.css">
4733 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4734 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4737 <example module="anchorScrollOffsetExample">
4738 <file name="index.html">
4739 <div class="fixed-header" ng-controller="headerCtrl">
4740 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4744 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4748 <file name="script.js">
4749 angular.module('anchorScrollOffsetExample', [])
4750 .run(['$anchorScroll', function($anchorScroll) {
4751 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4753 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4754 function ($anchorScroll, $location, $scope) {
4755 $scope.gotoAnchor = function(x) {
4756 var newHash = 'anchor' + x;
4757 if ($location.hash() !== newHash) {
4758 // set the $location.hash to `newHash` and
4759 // $anchorScroll will automatically scroll to it
4760 $location.hash('anchor' + x);
4762 // call $anchorScroll() explicitly,
4763 // since $location.hash hasn't changed
4770 <file name="style.css">
4776 border: 2px dashed DarkOrchid;
4777 padding: 10px 10px 200px 10px;
4781 background-color: rgba(0, 0, 0, 0.2);
4784 top: 0; left: 0; right: 0;
4788 display: inline-block;
4794 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4795 var document = $window.document;
4797 // Helper function to get first anchor from a NodeList
4798 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4799 // and working in all supported browsers.)
4800 function getFirstAnchor(list) {
4802 Array.prototype.some.call(list, function(element) {
4803 if (nodeName_(element) === 'a') {
4811 function getYOffset() {
4813 var offset = scroll.yOffset;
4815 if (isFunction(offset)) {
4817 } else if (isElement(offset)) {
4818 var elem = offset[0];
4819 var style = $window.getComputedStyle(elem);
4820 if (style.position !== 'fixed') {
4823 offset = elem.getBoundingClientRect().bottom;
4825 } else if (!isNumber(offset)) {
4832 function scrollTo(elem) {
4834 elem.scrollIntoView();
4836 var offset = getYOffset();
4839 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4840 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4841 // top of the viewport.
4843 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4844 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4845 // way down the page.
4847 // This is often the case for elements near the bottom of the page.
4849 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4850 // the top of the element and the offset, which is enough to align the top of `elem` at the
4851 // desired position.
4852 var elemTop = elem.getBoundingClientRect().top;
4853 $window.scrollBy(0, elemTop - offset);
4856 $window.scrollTo(0, 0);
4860 function scroll(hash) {
4861 hash = isString(hash) ? hash : $location.hash();
4864 // empty hash, scroll to the top of the page
4865 if (!hash) scrollTo(null);
4867 // element with given id
4868 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4870 // first anchor with given name :-D
4871 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4873 // no element and hash == 'top', scroll to the top of the page
4874 else if (hash === 'top') scrollTo(null);
4877 // does not scroll when user clicks on anchor link that is currently on
4878 // (no url change, no $location.hash() change), browser native does scroll
4879 if (autoScrollingEnabled) {
4880 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4881 function autoScrollWatchAction(newVal, oldVal) {
4882 // skip the initial scroll if $location.hash is empty
4883 if (newVal === oldVal && newVal === '') return;
4885 jqLiteDocumentLoaded(function() {
4886 $rootScope.$evalAsync(scroll);
4895 var $animateMinErr = minErr('$animate');
4896 var ELEMENT_NODE = 1;
4897 var NG_ANIMATE_CLASSNAME = 'ng-animate';
4899 function mergeClasses(a,b) {
4900 if (!a && !b) return '';
4903 if (isArray(a)) a = a.join(' ');
4904 if (isArray(b)) b = b.join(' ');
4908 function extractElementNode(element) {
4909 for (var i = 0; i < element.length; i++) {
4910 var elm = element[i];
4911 if (elm.nodeType === ELEMENT_NODE) {
4917 function splitClasses(classes) {
4918 if (isString(classes)) {
4919 classes = classes.split(' ');
4922 // Use createMap() to prevent class assumptions involving property names in
4924 var obj = createMap();
4925 forEach(classes, function(klass) {
4926 // sometimes the split leaves empty string values
4927 // incase extra spaces were applied to the options
4935 // if any other type of options value besides an Object value is
4936 // passed into the $animate.method() animation then this helper code
4937 // will be run which will ignore it. While this patch is not the
4938 // greatest solution to this, a lot of existing plugins depend on
4939 // $animate to either call the callback (< 1.2) or return a promise
4940 // that can be changed. This helper function ensures that the options
4941 // are wiped clean incase a callback function is provided.
4942 function prepareAnimateOptions(options) {
4943 return isObject(options)
4948 var $$CoreAnimateRunnerProvider = function() {
4949 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4950 function AnimateRunner() {}
4951 AnimateRunner.all = noop;
4952 AnimateRunner.chain = noop;
4953 AnimateRunner.prototype = {
4959 then: function(pass, fail) {
4960 return $q(function(resolve) {
4964 }).then(pass, fail);
4967 return AnimateRunner;
4971 // this is prefixed with Core since it conflicts with
4972 // the animateQueueProvider defined in ngAnimate/animateQueue.js
4973 var $$CoreAnimateQueueProvider = function() {
4974 var postDigestQueue = new HashMap();
4975 var postDigestElements = [];
4977 this.$get = ['$$AnimateRunner', '$rootScope',
4978 function($$AnimateRunner, $rootScope) {
4985 push: function(element, event, options, domOperation) {
4986 domOperation && domOperation();
4988 options = options || {};
4989 options.from && element.css(options.from);
4990 options.to && element.css(options.to);
4992 if (options.addClass || options.removeClass) {
4993 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
4996 return new $$AnimateRunner(); // jshint ignore:line
5001 function updateData(data, classes, value) {
5002 var changed = false;
5004 classes = isString(classes) ? classes.split(' ') :
5005 isArray(classes) ? classes : [];
5006 forEach(classes, function(className) {
5009 data[className] = value;
5016 function handleCSSClassChanges() {
5017 forEach(postDigestElements, function(element) {
5018 var data = postDigestQueue.get(element);
5020 var existing = splitClasses(element.attr('class'));
5023 forEach(data, function(status, className) {
5024 var hasClass = !!existing[className];
5025 if (status !== hasClass) {
5027 toAdd += (toAdd.length ? ' ' : '') + className;
5029 toRemove += (toRemove.length ? ' ' : '') + className;
5034 forEach(element, function(elm) {
5035 toAdd && jqLiteAddClass(elm, toAdd);
5036 toRemove && jqLiteRemoveClass(elm, toRemove);
5038 postDigestQueue.remove(element);
5041 postDigestElements.length = 0;
5045 function addRemoveClassesPostDigest(element, add, remove) {
5046 var data = postDigestQueue.get(element) || {};
5048 var classesAdded = updateData(data, add, true);
5049 var classesRemoved = updateData(data, remove, false);
5051 if (classesAdded || classesRemoved) {
5053 postDigestQueue.put(element, data);
5054 postDigestElements.push(element);
5056 if (postDigestElements.length === 1) {
5057 $rootScope.$$postDigest(handleCSSClassChanges);
5066 * @name $animateProvider
5069 * Default implementation of $animate that doesn't perform any animations, instead just
5070 * synchronously performs DOM updates and resolves the returned runner promise.
5072 * In order to enable animations the `ngAnimate` module has to be loaded.
5074 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5076 var $AnimateProvider = ['$provide', function($provide) {
5077 var provider = this;
5079 this.$$registeredAnimations = Object.create(null);
5083 * @name $animateProvider#register
5086 * Registers a new injectable animation factory function. The factory function produces the
5087 * animation object which contains callback functions for each event that is expected to be
5090 * * `eventFn`: `function(element, ... , doneFunction, options)`
5091 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5092 * on the type of animation additional arguments will be injected into the animation function. The
5093 * list below explains the function signatures for the different animation methods:
5095 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5096 * - addClass: function(element, addedClasses, doneFunction, options)
5097 * - removeClass: function(element, removedClasses, doneFunction, options)
5098 * - enter, leave, move: function(element, doneFunction, options)
5099 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5101 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5105 * //enter, leave, move signature
5106 * eventFn : function(element, done, options) {
5107 * //code to run the animation
5108 * //once complete, then run done()
5109 * return function endFunction(wasCancelled) {
5110 * //code to cancel the animation
5116 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5117 * @param {Function} factory The factory function that will be executed to return the animation
5120 this.register = function(name, factory) {
5121 if (name && name.charAt(0) !== '.') {
5122 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5125 var key = name + '-animation';
5126 provider.$$registeredAnimations[name.substr(1)] = key;
5127 $provide.factory(key, factory);
5132 * @name $animateProvider#classNameFilter
5135 * Sets and/or returns the CSS class regular expression that is checked when performing
5136 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5137 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5138 * When setting the `classNameFilter` value, animations will only be performed on elements
5139 * that successfully match the filter expression. This in turn can boost performance
5140 * for low-powered devices as well as applications containing a lot of structural operations.
5141 * @param {RegExp=} expression The className expression which will be checked against all animations
5142 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5144 this.classNameFilter = function(expression) {
5145 if (arguments.length === 1) {
5146 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5147 if (this.$$classNameFilter) {
5148 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5149 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5150 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5155 return this.$$classNameFilter;
5158 this.$get = ['$$animateQueue', function($$animateQueue) {
5159 function domInsert(element, parentElement, afterElement) {
5160 // if for some reason the previous element was removed
5161 // from the dom sometime before this code runs then let's
5162 // just stick to using the parent element as the anchor
5164 var afterNode = extractElementNode(afterElement);
5165 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5166 afterElement = null;
5169 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5175 * @description The $animate service exposes a series of DOM utility methods that provide support
5176 * for animation hooks. The default behavior is the application of DOM operations, however,
5177 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5178 * to ensure that animation runs with the triggered DOM operation.
5180 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5181 * included and only when it is active then the animation hooks that `$animate` triggers will be
5182 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5183 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5184 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5186 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5188 * To learn more about enabling animation support, click here to visit the
5189 * {@link ngAnimate ngAnimate module page}.
5192 // we don't call it directly since non-existant arguments may
5193 // be interpreted as null within the sub enabled function
5200 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5201 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5202 * is fired with the following params:
5205 * $animate.on('enter', container,
5206 * function callback(element, phase) {
5207 * // cool we detected an enter animation within the container
5212 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5213 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5214 * as well as among its children
5215 * @param {Function} callback the callback function that will be fired when the listener is triggered
5217 * The arguments present in the callback function are:
5218 * * `element` - The captured DOM element that the animation was fired on.
5219 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5221 on: $$animateQueue.on,
5226 * @name $animate#off
5228 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5229 * can be used in three different ways depending on the arguments:
5232 * // remove all the animation event listeners listening for `enter`
5233 * $animate.off('enter');
5235 * // remove all the animation event listeners listening for `enter` on the given element and its children
5236 * $animate.off('enter', container);
5238 * // remove the event listener function provided by `listenerFn` that is set
5239 * // to listen for `enter` on the given `element` as well as its children
5240 * $animate.off('enter', container, callback);
5243 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5244 * @param {DOMElement=} container the container element the event listener was placed on
5245 * @param {Function=} callback the callback function that was registered as the listener
5247 off: $$animateQueue.off,
5251 * @name $animate#pin
5253 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5254 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5255 * element despite being outside the realm of the application or within another application. Say for example if the application
5256 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5257 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5258 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5260 * Note that this feature is only active when the `ngAnimate` module is used.
5262 * @param {DOMElement} element the external element that will be pinned
5263 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5265 pin: $$animateQueue.pin,
5270 * @name $animate#enabled
5272 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5273 * function can be called in four ways:
5276 * // returns true or false
5277 * $animate.enabled();
5279 * // changes the enabled state for all animations
5280 * $animate.enabled(false);
5281 * $animate.enabled(true);
5283 * // returns true or false if animations are enabled for an element
5284 * $animate.enabled(element);
5286 * // changes the enabled state for an element and its children
5287 * $animate.enabled(element, true);
5288 * $animate.enabled(element, false);
5291 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5292 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5294 * @return {boolean} whether or not animations are enabled
5296 enabled: $$animateQueue.enabled,
5300 * @name $animate#cancel
5302 * @description Cancels the provided animation.
5304 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5306 cancel: function(runner) {
5307 runner.end && runner.end();
5313 * @name $animate#enter
5315 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5316 * as the first child within the `parent` element and then triggers an animation.
5317 * A promise is returned that will be resolved during the next digest once the animation
5320 * @param {DOMElement} element the element which will be inserted into the DOM
5321 * @param {DOMElement} parent the parent element which will append the element as
5322 * a child (so long as the after element is not present)
5323 * @param {DOMElement=} after the sibling element after which the element will be appended
5324 * @param {object=} options an optional collection of options/styles that will be applied to the element
5326 * @return {Promise} the animation callback promise
5328 enter: function(element, parent, after, options) {
5329 parent = parent && jqLite(parent);
5330 after = after && jqLite(after);
5331 parent = parent || after.parent();
5332 domInsert(element, parent, after);
5333 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5339 * @name $animate#move
5341 * @description Inserts (moves) the element into its new position in the DOM either after
5342 * the `after` element (if provided) or as the first child within the `parent` element
5343 * and then triggers an animation. A promise is returned that will be resolved
5344 * during the next digest once the animation has completed.
5346 * @param {DOMElement} element the element which will be moved into the new DOM position
5347 * @param {DOMElement} parent the parent element which will append the element as
5348 * a child (so long as the after element is not present)
5349 * @param {DOMElement=} after the sibling element after which the element will be appended
5350 * @param {object=} options an optional collection of options/styles that will be applied to the element
5352 * @return {Promise} the animation callback promise
5354 move: function(element, parent, after, options) {
5355 parent = parent && jqLite(parent);
5356 after = after && jqLite(after);
5357 parent = parent || after.parent();
5358 domInsert(element, parent, after);
5359 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5364 * @name $animate#leave
5366 * @description Triggers an animation and then removes the element from the DOM.
5367 * When the function is called a promise is returned that will be resolved during the next
5368 * digest once the animation has completed.
5370 * @param {DOMElement} element the element which will be removed from the DOM
5371 * @param {object=} options an optional collection of options/styles that will be applied to the element
5373 * @return {Promise} the animation callback promise
5375 leave: function(element, options) {
5376 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5383 * @name $animate#addClass
5386 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5387 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5388 * animation if element already contains the CSS class or if the class is removed at a later step.
5389 * Note that class-based animations are treated differently compared to structural animations
5390 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5391 * depending if CSS or JavaScript animations are used.
5393 * @param {DOMElement} element the element which the CSS classes will be applied to
5394 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5395 * @param {object=} options an optional collection of options/styles that will be applied to the element
5397 * @return {Promise} the animation callback promise
5399 addClass: function(element, className, options) {
5400 options = prepareAnimateOptions(options);
5401 options.addClass = mergeClasses(options.addclass, className);
5402 return $$animateQueue.push(element, 'addClass', options);
5407 * @name $animate#removeClass
5410 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5411 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5412 * animation if element does not contain the CSS class or if the class is added at a later step.
5413 * Note that class-based animations are treated differently compared to structural animations
5414 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5415 * depending if CSS or JavaScript animations are used.
5417 * @param {DOMElement} element the element which the CSS classes will be applied to
5418 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5419 * @param {object=} options an optional collection of options/styles that will be applied to the element
5421 * @return {Promise} the animation callback promise
5423 removeClass: function(element, className, options) {
5424 options = prepareAnimateOptions(options);
5425 options.removeClass = mergeClasses(options.removeClass, className);
5426 return $$animateQueue.push(element, 'removeClass', options);
5431 * @name $animate#setClass
5434 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5435 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5436 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5437 * passed. Note that class-based animations are treated differently compared to structural animations
5438 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5439 * depending if CSS or JavaScript animations are used.
5441 * @param {DOMElement} element the element which the CSS classes will be applied to
5442 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5443 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5444 * @param {object=} options an optional collection of options/styles that will be applied to the element
5446 * @return {Promise} the animation callback promise
5448 setClass: function(element, add, remove, options) {
5449 options = prepareAnimateOptions(options);
5450 options.addClass = mergeClasses(options.addClass, add);
5451 options.removeClass = mergeClasses(options.removeClass, remove);
5452 return $$animateQueue.push(element, 'setClass', options);
5457 * @name $animate#animate
5460 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5461 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5462 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5463 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5464 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5466 * @param {DOMElement} element the element which the CSS styles will be applied to
5467 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5468 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5469 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5470 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5471 * (Note that if no animation is detected then this value will not be appplied to the element.)
5472 * @param {object=} options an optional collection of options/styles that will be applied to the element
5474 * @return {Promise} the animation callback promise
5476 animate: function(element, from, to, className, options) {
5477 options = prepareAnimateOptions(options);
5478 options.from = options.from ? extend(options.from, from) : from;
5479 options.to = options.to ? extend(options.to, to) : to;
5481 className = className || 'ng-inline-animate';
5482 options.tempClasses = mergeClasses(options.tempClasses, className);
5483 return $$animateQueue.push(element, 'animate', options);
5495 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5496 * then the `$animateCss` service will actually perform animations.
5498 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5500 var $CoreAnimateCssProvider = function() {
5501 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5503 var RAFPromise = function() {};
5504 RAFPromise.prototype = {
5505 done: function(cancel) {
5506 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5511 cancel: function() {
5514 getPromise: function() {
5516 this.defer = $q.defer();
5518 return this.defer.promise;
5520 then: function(f1,f2) {
5521 return this.getPromise().then(f1,f2);
5523 'catch': function(f1) {
5524 return this.getPromise()['catch'](f1);
5526 'finally': function(f1) {
5527 return this.getPromise()['finally'](f1);
5531 return function(element, options) {
5532 // there is no point in applying the styles since
5533 // there is no animation that goes on at all in
5534 // this version of $animateCss.
5535 if (options.cleanupStyles) {
5536 options.from = options.to = null;
5540 element.css(options.from);
5541 options.from = null;
5544 var closed, runner = new RAFPromise();
5562 if (options.addClass) {
5563 element.addClass(options.addClass);
5564 options.addClass = null;
5566 if (options.removeClass) {
5567 element.removeClass(options.removeClass);
5568 options.removeClass = null;
5571 element.css(options.to);
5579 /* global stripHash: true */
5582 * ! This is a private undocumented service !
5587 * This object has two goals:
5589 * - hide all the global state in the browser caused by the window object
5590 * - abstract away all the browser specific features and inconsistencies
5592 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5593 * service, which can be used for convenient testing of the application without the interaction with
5594 * the real browser apis.
5597 * @param {object} window The global window object.
5598 * @param {object} document jQuery wrapped document.
5599 * @param {object} $log window.console or an object with the same interface.
5600 * @param {object} $sniffer $sniffer service
5602 function Browser(window, document, $log, $sniffer) {
5604 rawDocument = document[0],
5605 location = window.location,
5606 history = window.history,
5607 setTimeout = window.setTimeout,
5608 clearTimeout = window.clearTimeout,
5609 pendingDeferIds = {};
5611 self.isMock = false;
5613 var outstandingRequestCount = 0;
5614 var outstandingRequestCallbacks = [];
5616 // TODO(vojta): remove this temporary api
5617 self.$$completeOutstandingRequest = completeOutstandingRequest;
5618 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5621 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5622 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5624 function completeOutstandingRequest(fn) {
5626 fn.apply(null, sliceArgs(arguments, 1));
5628 outstandingRequestCount--;
5629 if (outstandingRequestCount === 0) {
5630 while (outstandingRequestCallbacks.length) {
5632 outstandingRequestCallbacks.pop()();
5641 function getHash(url) {
5642 var index = url.indexOf('#');
5643 return index === -1 ? '' : url.substr(index);
5648 * Note: this method is used only by scenario runner
5649 * TODO(vojta): prefix this method with $$ ?
5650 * @param {function()} callback Function that will be called when no outstanding request
5652 self.notifyWhenNoOutstandingRequests = function(callback) {
5653 if (outstandingRequestCount === 0) {
5656 outstandingRequestCallbacks.push(callback);
5660 //////////////////////////////////////////////////////////////
5662 //////////////////////////////////////////////////////////////
5664 var cachedState, lastHistoryState,
5665 lastBrowserUrl = location.href,
5666 baseElement = document.find('base'),
5667 pendingLocation = null;
5670 lastHistoryState = cachedState;
5673 * @name $browser#url
5677 * Without any argument, this method just returns current value of location.href.
5680 * With at least one argument, this method sets url to new value.
5681 * If html5 history api supported, pushState/replaceState is used, otherwise
5682 * location.href/location.replace is used.
5683 * Returns its own instance to allow chaining
5685 * NOTE: this api is intended for use only by the $location service. Please use the
5686 * {@link ng.$location $location service} to change url.
5688 * @param {string} url New url (when used as setter)
5689 * @param {boolean=} replace Should new url replace current history record?
5690 * @param {object=} state object to use with pushState/replaceState
5692 self.url = function(url, replace, state) {
5693 // In modern browsers `history.state` is `null` by default; treating it separately
5694 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5695 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5696 if (isUndefined(state)) {
5700 // Android Browser BFCache causes location, history reference to become stale.
5701 if (location !== window.location) location = window.location;
5702 if (history !== window.history) history = window.history;
5706 var sameState = lastHistoryState === state;
5708 // Don't change anything if previous and current URLs and states match. This also prevents
5709 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5710 // See https://github.com/angular/angular.js/commit/ffb2701
5711 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5714 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5715 lastBrowserUrl = url;
5716 lastHistoryState = state;
5717 // Don't use history API if only the hash changed
5718 // due to a bug in IE10/IE11 which leads
5719 // to not firing a `hashchange` nor `popstate` event
5720 // in some cases (see #9143).
5721 if ($sniffer.history && (!sameBase || !sameState)) {
5722 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5724 // Do the assignment again so that those two variables are referentially identical.
5725 lastHistoryState = cachedState;
5727 if (!sameBase || pendingLocation) {
5728 pendingLocation = url;
5731 location.replace(url);
5732 } else if (!sameBase) {
5733 location.href = url;
5735 location.hash = getHash(url);
5737 if (location.href !== url) {
5738 pendingLocation = url;
5744 // - pendingLocation is needed as browsers don't allow to read out
5745 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5746 // https://openradar.appspot.com/22186109).
5747 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5748 return pendingLocation || location.href.replace(/%27/g,"'");
5753 * @name $browser#state
5756 * This method is a getter.
5758 * Return history.state or null if history.state is undefined.
5760 * @returns {object} state
5762 self.state = function() {
5766 var urlChangeListeners = [],
5767 urlChangeInit = false;
5769 function cacheStateAndFireUrlChange() {
5770 pendingLocation = null;
5775 function getCurrentState() {
5777 return history.state;
5779 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5783 // This variable should be used *only* inside the cacheState function.
5784 var lastCachedState = null;
5785 function cacheState() {
5786 // This should be the only place in $browser where `history.state` is read.
5787 cachedState = getCurrentState();
5788 cachedState = isUndefined(cachedState) ? null : cachedState;
5790 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5791 if (equals(cachedState, lastCachedState)) {
5792 cachedState = lastCachedState;
5794 lastCachedState = cachedState;
5797 function fireUrlChange() {
5798 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5802 lastBrowserUrl = self.url();
5803 lastHistoryState = cachedState;
5804 forEach(urlChangeListeners, function(listener) {
5805 listener(self.url(), cachedState);
5810 * @name $browser#onUrlChange
5813 * Register callback function that will be called, when url changes.
5815 * It's only called when the url is changed from outside of angular:
5816 * - user types different url into address bar
5817 * - user clicks on history (forward/back) button
5818 * - user clicks on a link
5820 * It's not called when url is changed by $browser.url() method
5822 * The listener gets called with new url as parameter.
5824 * NOTE: this api is intended for use only by the $location service. Please use the
5825 * {@link ng.$location $location service} to monitor url changes in angular apps.
5827 * @param {function(string)} listener Listener function to be called when url changes.
5828 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5830 self.onUrlChange = function(callback) {
5831 // TODO(vojta): refactor to use node's syntax for events
5832 if (!urlChangeInit) {
5833 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5834 // don't fire popstate when user change the address bar and don't fire hashchange when url
5835 // changed by push/replaceState
5837 // html5 history api - popstate event
5838 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5840 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5842 urlChangeInit = true;
5845 urlChangeListeners.push(callback);
5851 * Remove popstate and hashchange handler from window.
5853 * NOTE: this api is intended for use only by $rootScope.
5855 self.$$applicationDestroyed = function() {
5856 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5860 * Checks whether the url has changed outside of Angular.
5861 * Needs to be exported to be able to check for changes that have been done in sync,
5862 * as hashchange/popstate events fire in async.
5864 self.$$checkUrlChange = fireUrlChange;
5866 //////////////////////////////////////////////////////////////
5868 //////////////////////////////////////////////////////////////
5871 * @name $browser#baseHref
5874 * Returns current <base href>
5875 * (always relative - without domain)
5877 * @returns {string} The current base href
5879 self.baseHref = function() {
5880 var href = baseElement.attr('href');
5881 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5885 * @name $browser#defer
5886 * @param {function()} fn A function, who's execution should be deferred.
5887 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5888 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5891 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5893 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5894 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5895 * via `$browser.defer.flush()`.
5898 self.defer = function(fn, delay) {
5900 outstandingRequestCount++;
5901 timeoutId = setTimeout(function() {
5902 delete pendingDeferIds[timeoutId];
5903 completeOutstandingRequest(fn);
5905 pendingDeferIds[timeoutId] = true;
5911 * @name $browser#defer.cancel
5914 * Cancels a deferred task identified with `deferId`.
5916 * @param {*} deferId Token returned by the `$browser.defer` function.
5917 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5920 self.defer.cancel = function(deferId) {
5921 if (pendingDeferIds[deferId]) {
5922 delete pendingDeferIds[deferId];
5923 clearTimeout(deferId);
5924 completeOutstandingRequest(noop);
5932 function $BrowserProvider() {
5933 this.$get = ['$window', '$log', '$sniffer', '$document',
5934 function($window, $log, $sniffer, $document) {
5935 return new Browser($window, $document, $log, $sniffer);
5941 * @name $cacheFactory
5944 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5949 * var cache = $cacheFactory('cacheId');
5950 * expect($cacheFactory.get('cacheId')).toBe(cache);
5951 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5953 * cache.put("key", "value");
5954 * cache.put("another key", "another value");
5956 * // We've specified no options on creation
5957 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5962 * @param {string} cacheId Name or id of the newly created cache.
5963 * @param {object=} options Options object that specifies the cache behavior. Properties:
5965 * - `{number=}` `capacity` — turns the cache into LRU cache.
5967 * @returns {object} Newly created cache object with the following set of methods:
5969 * - `{object}` `info()` — Returns id, size, and options of cache.
5970 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5972 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5973 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5974 * - `{void}` `removeAll()` — Removes all cached values.
5975 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5978 <example module="cacheExampleApp">
5979 <file name="index.html">
5980 <div ng-controller="CacheController">
5981 <input ng-model="newCacheKey" placeholder="Key">
5982 <input ng-model="newCacheValue" placeholder="Value">
5983 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5985 <p ng-if="keys.length">Cached Values</p>
5986 <div ng-repeat="key in keys">
5987 <span ng-bind="key"></span>
5989 <b ng-bind="cache.get(key)"></b>
5993 <div ng-repeat="(key, value) in cache.info()">
5994 <span ng-bind="key"></span>
5996 <b ng-bind="value"></b>
6000 <file name="script.js">
6001 angular.module('cacheExampleApp', []).
6002 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6004 $scope.cache = $cacheFactory('cacheId');
6005 $scope.put = function(key, value) {
6006 if (angular.isUndefined($scope.cache.get(key))) {
6007 $scope.keys.push(key);
6009 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6013 <file name="style.css">
6020 function $CacheFactoryProvider() {
6022 this.$get = function() {
6025 function cacheFactory(cacheId, options) {
6026 if (cacheId in caches) {
6027 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
6031 stats = extend({}, options, {id: cacheId}),
6033 capacity = (options && options.capacity) || Number.MAX_VALUE,
6034 lruHash = createMap(),
6040 * @name $cacheFactory.Cache
6043 * A cache object used to store and retrieve data, primarily used by
6044 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6045 * templates and other data.
6048 * angular.module('superCache')
6049 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6050 * return $cacheFactory('super-cache');
6057 * it('should behave like a cache', inject(function(superCache) {
6058 * superCache.put('key', 'value');
6059 * superCache.put('another key', 'another value');
6061 * expect(superCache.info()).toEqual({
6062 * id: 'super-cache',
6066 * superCache.remove('another key');
6067 * expect(superCache.get('another key')).toBeUndefined();
6069 * superCache.removeAll();
6070 * expect(superCache.info()).toEqual({
6071 * id: 'super-cache',
6077 return caches[cacheId] = {
6081 * @name $cacheFactory.Cache#put
6085 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6086 * retrieved later, and incrementing the size of the cache if the key was not already
6087 * present in the cache. If behaving like an LRU cache, it will also remove stale
6088 * entries from the set.
6090 * It will not insert undefined values into the cache.
6092 * @param {string} key the key under which the cached data is stored.
6093 * @param {*} value the value to store alongside the key. If it is undefined, the key
6094 * will not be stored.
6095 * @returns {*} the value stored.
6097 put: function(key, value) {
6098 if (isUndefined(value)) return;
6099 if (capacity < Number.MAX_VALUE) {
6100 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6105 if (!(key in data)) size++;
6108 if (size > capacity) {
6109 this.remove(staleEnd.key);
6117 * @name $cacheFactory.Cache#get
6121 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6123 * @param {string} key the key of the data to be retrieved
6124 * @returns {*} the value stored.
6126 get: function(key) {
6127 if (capacity < Number.MAX_VALUE) {
6128 var lruEntry = lruHash[key];
6130 if (!lruEntry) return;
6141 * @name $cacheFactory.Cache#remove
6145 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6147 * @param {string} key the key of the entry to be removed
6149 remove: function(key) {
6150 if (capacity < Number.MAX_VALUE) {
6151 var lruEntry = lruHash[key];
6153 if (!lruEntry) return;
6155 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6156 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6157 link(lruEntry.n,lruEntry.p);
6159 delete lruHash[key];
6162 if (!(key in data)) return;
6171 * @name $cacheFactory.Cache#removeAll
6175 * Clears the cache object of any entries.
6177 removeAll: function() {
6180 lruHash = createMap();
6181 freshEnd = staleEnd = null;
6187 * @name $cacheFactory.Cache#destroy
6191 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6192 * removing it from the {@link $cacheFactory $cacheFactory} set.
6194 destroy: function() {
6198 delete caches[cacheId];
6204 * @name $cacheFactory.Cache#info
6208 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6210 * @returns {object} an object with the following properties:
6212 * <li>**id**: the id of the cache instance</li>
6213 * <li>**size**: the number of entries kept in the cache instance</li>
6214 * <li>**...**: any additional properties from the options object when creating the
6219 return extend({}, stats, {size: size});
6225 * makes the `entry` the freshEnd of the LRU linked list
6227 function refresh(entry) {
6228 if (entry != freshEnd) {
6231 } else if (staleEnd == entry) {
6235 link(entry.n, entry.p);
6236 link(entry, freshEnd);
6244 * bidirectionally links two entries of the LRU linked list
6246 function link(nextEntry, prevEntry) {
6247 if (nextEntry != prevEntry) {
6248 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6249 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6257 * @name $cacheFactory#info
6260 * Get information about all the caches that have been created
6262 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6264 cacheFactory.info = function() {
6266 forEach(caches, function(cache, cacheId) {
6267 info[cacheId] = cache.info();
6275 * @name $cacheFactory#get
6278 * Get access to a cache object by the `cacheId` used when it was created.
6280 * @param {string} cacheId Name or id of a cache to access.
6281 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6283 cacheFactory.get = function(cacheId) {
6284 return caches[cacheId];
6288 return cacheFactory;
6294 * @name $templateCache
6297 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6298 * can load templates directly into the cache in a `script` tag, or by consuming the
6299 * `$templateCache` service directly.
6301 * Adding via the `script` tag:
6304 * <script type="text/ng-template" id="templateId.html">
6305 * <p>This is the content of the template</p>
6309 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6310 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6311 * element with ng-app attribute), otherwise the template will be ignored.
6313 * Adding via the `$templateCache` service:
6316 * var myApp = angular.module('myApp', []);
6317 * myApp.run(function($templateCache) {
6318 * $templateCache.put('templateId.html', 'This is the content of the template');
6322 * To retrieve the template later, simply use it in your HTML:
6324 * <div ng-include=" 'templateId.html' "></div>
6327 * or get it via Javascript:
6329 * $templateCache.get('templateId.html')
6332 * See {@link ng.$cacheFactory $cacheFactory}.
6335 function $TemplateCacheProvider() {
6336 this.$get = ['$cacheFactory', function($cacheFactory) {
6337 return $cacheFactory('templates');
6341 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6342 * Any commits to this file should be reviewed with security in mind. *
6343 * Changes to this file can potentially create security vulnerabilities. *
6344 * An approval from 2 Core members with history of modifying *
6345 * this file is required. *
6347 * Does the change somehow allow for arbitrary javascript to be executed? *
6348 * Or allows for someone to change the prototype of built-in objects? *
6349 * Or gives undesired access to variables likes document or window? *
6350 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6352 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6354 * DOM-related variables:
6356 * - "node" - DOM Node
6357 * - "element" - DOM Element or Node
6358 * - "$node" or "$element" - jqLite-wrapped node or element
6361 * Compiler related stuff:
6363 * - "linkFn" - linking fn of a single directive
6364 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6365 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6366 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6376 * Compiles an HTML string or DOM into a template and produces a template function, which
6377 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6379 * The compilation is a process of walking the DOM tree and matching DOM elements to
6380 * {@link ng.$compileProvider#directive directives}.
6382 * <div class="alert alert-warning">
6383 * **Note:** This document is an in-depth reference of all directive options.
6384 * For a gentle introduction to directives with examples of common use cases,
6385 * see the {@link guide/directive directive guide}.
6388 * ## Comprehensive Directive API
6390 * There are many different options for a directive.
6392 * The difference resides in the return value of the factory function.
6393 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6394 * or just the `postLink` function (all other properties will have the default values).
6396 * <div class="alert alert-success">
6397 * **Best Practice:** It's recommended to use the "directive definition object" form.
6400 * Here's an example directive declared with a Directive Definition Object:
6403 * var myModule = angular.module(...);
6405 * myModule.directive('directiveName', function factory(injectables) {
6406 * var directiveDefinitionObject = {
6408 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6410 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6411 * transclude: false,
6413 * templateNamespace: 'html',
6415 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6416 * controllerAs: 'stringIdentifier',
6417 * bindToController: false,
6418 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6419 * compile: function compile(tElement, tAttrs, transclude) {
6421 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6422 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6425 * // return function postLink( ... ) { ... }
6429 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6430 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6433 * // link: function postLink( ... ) { ... }
6435 * return directiveDefinitionObject;
6439 * <div class="alert alert-warning">
6440 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6443 * Therefore the above can be simplified as:
6446 * var myModule = angular.module(...);
6448 * myModule.directive('directiveName', function factory(injectables) {
6449 * var directiveDefinitionObject = {
6450 * link: function postLink(scope, iElement, iAttrs) { ... }
6452 * return directiveDefinitionObject;
6454 * // return function postLink(scope, iElement, iAttrs) { ... }
6460 * ### Directive Definition Object
6462 * The directive definition object provides instructions to the {@link ng.$compile
6463 * compiler}. The attributes are:
6465 * #### `multiElement`
6466 * When this property is set to true, the HTML compiler will collect DOM nodes between
6467 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6468 * together as the directive elements. It is recommended that this feature be used on directives
6469 * which are not strictly behavioural (such as {@link ngClick}), and which
6470 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6473 * When there are multiple directives defined on a single DOM element, sometimes it
6474 * is necessary to specify the order in which the directives are applied. The `priority` is used
6475 * to sort the directives before their `compile` functions get called. Priority is defined as a
6476 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6477 * are also run in priority order, but post-link functions are run in reverse order. The order
6478 * of directives with the same priority is undefined. The default priority is `0`.
6481 * If set to true then the current `priority` will be the last set of directives
6482 * which will execute (any directives at the current priority will still execute
6483 * as the order of execution on same `priority` is undefined). Note that expressions
6484 * and other directives used in the directive's template will also be excluded from execution.
6487 * The scope property can be `true`, an object or a falsy value:
6489 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6491 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6492 * the directive's element. If multiple directives on the same element request a new scope,
6493 * only one new scope is created. The new scope rule does not apply for the root of the template
6494 * since the root of the template always gets a new scope.
6496 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6497 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6498 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6499 * data in the parent scope.
6501 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6502 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6503 * the object hash map to the name of the property on the isolate scope; the values define how the property
6504 * is bound to the parent scope, via matching attributes on the directive's element:
6506 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6507 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6508 * attribute name is assumed to be the same as the local name.
6509 * Given `<widget my-attr="hello {{name}}">` and widget definition
6510 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6511 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6512 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6515 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6516 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6517 * name is specified then the attribute name is assumed to be the same as the local name.
6518 * Given `<widget my-attr="parentModel">` and widget definition of
6519 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6520 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6521 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6522 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6523 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6524 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6525 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6527 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6528 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6529 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6530 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6531 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6532 * pass data from the isolated scope via an expression to the parent scope, this can be
6533 * done by passing a map of local variable names and values into the expression wrapper fn.
6534 * For example, if the expression is `increment(amount)` then we can specify the amount value
6535 * by calling the `localFn` as `localFn({amount: 22})`.
6537 * In general it's possible to apply more than one directive to one element, but there might be limitations
6538 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6539 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6541 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6542 * * **child scope** + **no scope** => Both directives will share one single child scope
6543 * * **child scope** + **child scope** => Both directives will share one single child scope
6544 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6545 * its parent's scope
6546 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6547 * be applied to the same element.
6548 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6549 * cannot be applied to the same element.
6552 * #### `bindToController`
6553 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6554 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6555 * is instantiated, the initial values of the isolate scope bindings are already available.
6558 * Controller constructor function. The controller is instantiated before the
6559 * pre-linking phase and can be accessed by other directives (see
6560 * `require` attribute). This allows the directives to communicate with each other and augment
6561 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6563 * * `$scope` - Current scope associated with the element
6564 * * `$element` - Current element
6565 * * `$attrs` - Current attributes object for the element
6566 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6567 * `function([scope], cloneLinkingFn, futureParentElement)`.
6568 * * `scope`: optional argument to override the scope.
6569 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6570 * * `futureParentElement`:
6571 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6572 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6573 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6574 * and when the `cloneLinkinFn` is passed,
6575 * as those elements need to created and cloned in a special way when they are defined outside their
6576 * usual containers (e.g. like `<svg>`).
6577 * * See also the `directive.templateNamespace` property.
6581 * Require another directive and inject its controller as the fourth argument to the linking function. The
6582 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6583 * injected argument will be an array in corresponding order. If no such directive can be
6584 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6585 * is specified, in which case error checking is skipped). The name can be prefixed with:
6587 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6588 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6589 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6590 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6591 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6592 * `null` to the `link` fn if not found.
6593 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6594 * `null` to the `link` fn if not found.
6597 * #### `controllerAs`
6598 * Identifier name for a reference to the controller in the directive's scope.
6599 * This allows the controller to be referenced from the directive template. This is especially
6600 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6601 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6602 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6606 * String of subset of `EACM` which restricts the directive to a specific directive
6607 * declaration style. If omitted, the defaults (elements and attributes) are used.
6609 * * `E` - Element name (default): `<my-directive></my-directive>`
6610 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6611 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6612 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6615 * #### `templateNamespace`
6616 * String representing the document type used by the markup in the template.
6617 * AngularJS needs this information as those elements need to be created and cloned
6618 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6620 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6621 * top-level elements such as `<svg>` or `<math>`.
6622 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6623 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6625 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6628 * HTML markup that may:
6629 * * Replace the contents of the directive's element (default).
6630 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6631 * * Wrap the contents of the directive's element (if `transclude` is true).
6635 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6636 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6637 * function api below) and returns a string value.
6640 * #### `templateUrl`
6641 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6643 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6644 * for later when the template has been resolved. In the meantime it will continue to compile and link
6645 * sibling and parent elements as though this element had not contained any directives.
6647 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6648 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6649 * case when only one deeply nested directive has `templateUrl`.
6651 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6653 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6654 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6655 * a string value representing the url. In either case, the template URL is passed through {@link
6656 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6659 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6660 * specify what the template should replace. Defaults to `false`.
6662 * * `true` - the template will replace the directive's element.
6663 * * `false` - the template will replace the contents of the directive's element.
6665 * The replacement process migrates all of the attributes / classes from the old element to the new
6666 * one. See the {@link guide/directive#template-expanding-directive
6667 * Directives Guide} for an example.
6669 * There are very few scenarios where element replacement is required for the application function,
6670 * the main one being reusable custom components that are used within SVG contexts
6671 * (because SVG doesn't work with custom elements in the DOM tree).
6674 * Extract the contents of the element where the directive appears and make it available to the directive.
6675 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6676 * {@link $compile#transclusion Transclusion} section below.
6678 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6679 * directive's element or the entire element:
6681 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6682 * * `'element'` - transclude the whole of the directive's element including any directives on this
6683 * element that defined at a lower priority than this directive. When used, the `template`
6684 * property is ignored.
6690 * function compile(tElement, tAttrs, transclude) { ... }
6693 * The compile function deals with transforming the template DOM. Since most directives do not do
6694 * template transformation, it is not used often. The compile function takes the following arguments:
6696 * * `tElement` - template element - The element where the directive has been declared. It is
6697 * safe to do template transformation on the element and child elements only.
6699 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6700 * between all directive compile functions.
6702 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6704 * <div class="alert alert-warning">
6705 * **Note:** The template instance and the link instance may be different objects if the template has
6706 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6707 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6708 * should be done in a linking function rather than in a compile function.
6711 * <div class="alert alert-warning">
6712 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6713 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6714 * stack overflow errors.
6716 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6717 * a directive's template instead of relying on automatic template compilation via `template` or
6718 * `templateUrl` declaration or manual compilation inside the compile function.
6721 * <div class="alert alert-danger">
6722 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6723 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6724 * to the link function instead.
6727 * A compile function can have a return value which can be either a function or an object.
6729 * * returning a (post-link) function - is equivalent to registering the linking function via the
6730 * `link` property of the config object when the compile function is empty.
6732 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6733 * control when a linking function should be called during the linking phase. See info about
6734 * pre-linking and post-linking functions below.
6738 * This property is used only if the `compile` property is not defined.
6741 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6744 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6745 * executed after the template has been cloned. This is where most of the directive logic will be
6748 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6749 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6751 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6752 * manipulate the children of the element only in `postLink` function since the children have
6753 * already been linked.
6755 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6756 * between all directive linking functions.
6758 * * `controller` - the directive's required controller instance(s) - Instances are shared
6759 * among all directives, which allows the directives to use the controllers as a communication
6760 * channel. The exact value depends on the directive's `require` property:
6761 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6762 * * `string`: the controller instance
6763 * * `array`: array of controller instances
6765 * If a required controller cannot be found, and it is optional, the instance is `null`,
6766 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6768 * Note that you can also require the directive's own controller - it will be made available like
6769 * any other controller.
6771 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6772 * This is the same as the `$transclude`
6773 * parameter of directive controllers, see there for details.
6774 * `function([scope], cloneLinkingFn, futureParentElement)`.
6776 * #### Pre-linking function
6778 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6779 * compiler linking function will fail to locate the correct elements for linking.
6781 * #### Post-linking function
6783 * Executed after the child elements are linked.
6785 * Note that child elements that contain `templateUrl` directives will not have been compiled
6786 * and linked since they are waiting for their template to load asynchronously and their own
6787 * compilation and linking has been suspended until that occurs.
6789 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6790 * for their async templates to be resolved.
6795 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6796 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6797 * scope from where they were taken.
6799 * Transclusion is used (often with {@link ngTransclude}) to insert the
6800 * original contents of a directive's element into a specified place in the template of the directive.
6801 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6802 * content has access to the properties on the scope from which it was taken, even if the directive
6803 * has isolated scope.
6804 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6806 * This makes it possible for the widget to have private state for its template, while the transcluded
6807 * content has access to its originating scope.
6809 * <div class="alert alert-warning">
6810 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6811 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6812 * Testing Transclusion Directives}.
6815 * #### Transclusion Functions
6817 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6818 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6819 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6821 * <div class="alert alert-info">
6822 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6823 * ngTransclude will deal with it for us.
6826 * If you want to manually control the insertion and removal of the transcluded content in your directive
6827 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6828 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6830 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6831 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6832 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6834 * <div class="alert alert-info">
6835 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6836 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6839 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6840 * attach function**:
6843 * var transcludedContent, transclusionScope;
6845 * $transclude(function(clone, scope) {
6846 * element.append(clone);
6847 * transcludedContent = clone;
6848 * transclusionScope = scope;
6852 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6853 * associated transclusion scope:
6856 * transcludedContent.remove();
6857 * transclusionScope.$destroy();
6860 * <div class="alert alert-info">
6861 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6862 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6863 * then you are also responsible for calling `$destroy` on the transclusion scope.
6866 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6867 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6868 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6871 * #### Transclusion Scopes
6873 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6874 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6875 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6878 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6884 * <div transclusion>
6890 * The `$parent` scope hierarchy will look like this:
6898 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6909 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6910 * `link()` or `compile()` functions. It has a variety of uses.
6912 * accessing *Normalized attribute names:*
6913 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6914 * the attributes object allows for normalized access to
6917 * * *Directive inter-communication:* All directives share the same instance of the attributes
6918 * object which allows the directives to use the attributes object as inter directive
6921 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6922 * allowing other directives to read the interpolated value.
6924 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6925 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6926 * the only way to easily get the actual value because during the linking phase the interpolation
6927 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6930 * function linkingFn(scope, elm, attrs, ctrl) {
6931 * // get the attribute value
6932 * console.log(attrs.ngModel);
6934 * // change the attribute
6935 * attrs.$set('ngModel', 'new value');
6937 * // observe changes to interpolated attribute
6938 * attrs.$observe('ngModel', function(value) {
6939 * console.log('ngModel has changed value to ' + value);
6946 * <div class="alert alert-warning">
6947 * **Note**: Typically directives are registered with `module.directive`. The example below is
6948 * to illustrate how `$compile` works.
6951 <example module="compileExample">
6952 <file name="index.html">
6954 angular.module('compileExample', [], function($compileProvider) {
6955 // configure new 'compile' directive by passing a directive
6956 // factory function. The factory function injects the '$compile'
6957 $compileProvider.directive('compile', function($compile) {
6958 // directive factory creates a link function
6959 return function(scope, element, attrs) {
6962 // watch the 'compile' expression for changes
6963 return scope.$eval(attrs.compile);
6966 // when the 'compile' expression changes
6967 // assign it into the current DOM
6968 element.html(value);
6970 // compile the new DOM and link it to the current
6972 // NOTE: we only compile .childNodes so that
6973 // we don't get into infinite loop compiling ourselves
6974 $compile(element.contents())(scope);
6980 .controller('GreeterController', ['$scope', function($scope) {
6981 $scope.name = 'Angular';
6982 $scope.html = 'Hello {{name}}';
6985 <div ng-controller="GreeterController">
6986 <input ng-model="name"> <br/>
6987 <textarea ng-model="html"></textarea> <br/>
6988 <div compile="html"></div>
6991 <file name="protractor.js" type="protractor">
6992 it('should auto compile', function() {
6993 var textarea = $('textarea');
6994 var output = $('div[compile]');
6995 // The initial state reads 'Hello Angular'.
6996 expect(output.getText()).toBe('Hello Angular');
6998 textarea.sendKeys('{{name}}!');
6999 expect(output.getText()).toBe('Angular!');
7006 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7007 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7009 * <div class="alert alert-danger">
7010 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7011 * e.g. will not use the right outer scope. Please pass the transclude function as a
7012 * `parentBoundTranscludeFn` to the link function instead.
7015 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7016 * root element(s), not their children)
7017 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7018 * (a DOM element/tree) to a scope. Where:
7020 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7021 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7022 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7023 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7024 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7026 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7027 * * `scope` - is the current scope with which the linking function is working with.
7029 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7030 * keys may be used to control linking behavior:
7032 * * `parentBoundTranscludeFn` - the transclude function made available to
7033 * directives; if given, it will be passed through to the link functions of
7034 * directives found in `element` during compilation.
7035 * * `transcludeControllers` - an object hash with keys that map controller names
7036 * to controller instances; if given, it will make the controllers
7037 * available to directives.
7038 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7039 * the cloned elements; only needed for transcludes that are allowed to contain non html
7040 * elements (e.g. SVG elements). See also the directive.controller property.
7042 * Calling the linking function returns the element of the template. It is either the original
7043 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7045 * After linking the view is not updated until after a call to $digest which typically is done by
7046 * Angular automatically.
7048 * If you need access to the bound view, there are two ways to do it:
7050 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7051 * before you send them to the compiler and keep this reference around.
7053 * var element = $compile('<p>{{total}}</p>')(scope);
7056 * - if on the other hand, you need the element to be cloned, the view reference from the original
7057 * example would not point to the clone, but rather to the original template that was cloned. In
7058 * this case, you can access the clone via the cloneAttachFn:
7060 * var templateElement = angular.element('<p>{{total}}</p>'),
7063 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7064 * //attach the clone to DOM document at the right place
7067 * //now we have reference to the cloned DOM via `clonedElement`
7071 * For information on how the compiler works, see the
7072 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
7075 var $compileMinErr = minErr('$compile');
7079 * @name $compileProvider
7083 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7084 function $CompileProvider($provide, $$sanitizeUriProvider) {
7085 var hasDirectives = {},
7086 Suffix = 'Directive',
7087 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7088 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7089 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7090 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7092 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7093 // The assumption is that future DOM event attribute names will begin with
7094 // 'on' and be composed of only English letters.
7095 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7097 function parseIsolateBindings(scope, directiveName, isController) {
7098 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7102 forEach(scope, function(definition, scopeName) {
7103 var match = definition.match(LOCAL_REGEXP);
7106 throw $compileMinErr('iscp',
7107 "Invalid {3} for directive '{0}'." +
7108 " Definition: {... {1}: '{2}' ...}",
7109 directiveName, scopeName, definition,
7110 (isController ? "controller bindings definition" :
7111 "isolate scope definition"));
7114 bindings[scopeName] = {
7116 collection: match[2] === '*',
7117 optional: match[3] === '?',
7118 attrName: match[4] || scopeName
7125 function parseDirectiveBindings(directive, directiveName) {
7128 bindToController: null
7130 if (isObject(directive.scope)) {
7131 if (directive.bindToController === true) {
7132 bindings.bindToController = parseIsolateBindings(directive.scope,
7133 directiveName, true);
7134 bindings.isolateScope = {};
7136 bindings.isolateScope = parseIsolateBindings(directive.scope,
7137 directiveName, false);
7140 if (isObject(directive.bindToController)) {
7141 bindings.bindToController =
7142 parseIsolateBindings(directive.bindToController, directiveName, true);
7144 if (isObject(bindings.bindToController)) {
7145 var controller = directive.controller;
7146 var controllerAs = directive.controllerAs;
7148 // There is no controller, there may or may not be a controllerAs property
7149 throw $compileMinErr('noctrl',
7150 "Cannot bind to controller without directive '{0}'s controller.",
7152 } else if (!identifierForController(controller, controllerAs)) {
7153 // There is a controller, but no identifier or controllerAs property
7154 throw $compileMinErr('noident',
7155 "Cannot bind to controller without identifier for directive '{0}'.",
7162 function assertValidDirectiveName(name) {
7163 var letter = name.charAt(0);
7164 if (!letter || letter !== lowercase(letter)) {
7165 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7167 if (name !== name.trim()) {
7168 throw $compileMinErr('baddir',
7169 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7176 * @name $compileProvider#directive
7180 * Register a new directive with the compiler.
7182 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7183 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7184 * names and the values are the factories.
7185 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7186 * {@link guide/directive} for more info.
7187 * @returns {ng.$compileProvider} Self for chaining.
7189 this.directive = function registerDirective(name, directiveFactory) {
7190 assertNotHasOwnProperty(name, 'directive');
7191 if (isString(name)) {
7192 assertValidDirectiveName(name);
7193 assertArg(directiveFactory, 'directiveFactory');
7194 if (!hasDirectives.hasOwnProperty(name)) {
7195 hasDirectives[name] = [];
7196 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7197 function($injector, $exceptionHandler) {
7198 var directives = [];
7199 forEach(hasDirectives[name], function(directiveFactory, index) {
7201 var directive = $injector.invoke(directiveFactory);
7202 if (isFunction(directive)) {
7203 directive = { compile: valueFn(directive) };
7204 } else if (!directive.compile && directive.link) {
7205 directive.compile = valueFn(directive.link);
7207 directive.priority = directive.priority || 0;
7208 directive.index = index;
7209 directive.name = directive.name || name;
7210 directive.require = directive.require || (directive.controller && directive.name);
7211 directive.restrict = directive.restrict || 'EA';
7212 var bindings = directive.$$bindings =
7213 parseDirectiveBindings(directive, directive.name);
7214 if (isObject(bindings.isolateScope)) {
7215 directive.$$isolateBindings = bindings.isolateScope;
7217 directive.$$moduleName = directiveFactory.$$moduleName;
7218 directives.push(directive);
7220 $exceptionHandler(e);
7226 hasDirectives[name].push(directiveFactory);
7228 forEach(name, reverseParams(registerDirective));
7236 * @name $compileProvider#aHrefSanitizationWhitelist
7240 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7241 * urls during a[href] sanitization.
7243 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7245 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7246 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7247 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7248 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7250 * @param {RegExp=} regexp New regexp to whitelist urls with.
7251 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7252 * chaining otherwise.
7254 this.aHrefSanitizationWhitelist = function(regexp) {
7255 if (isDefined(regexp)) {
7256 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7259 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7266 * @name $compileProvider#imgSrcSanitizationWhitelist
7270 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7271 * urls during img[src] sanitization.
7273 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7275 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7276 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7277 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7278 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7280 * @param {RegExp=} regexp New regexp to whitelist urls with.
7281 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7282 * chaining otherwise.
7284 this.imgSrcSanitizationWhitelist = function(regexp) {
7285 if (isDefined(regexp)) {
7286 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7289 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7295 * @name $compileProvider#debugInfoEnabled
7297 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7298 * current debugInfoEnabled state
7299 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7304 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7305 * binding information and a reference to the current scope on to DOM elements.
7306 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7307 * * `ng-binding` CSS class
7308 * * `$binding` data property containing an array of the binding expressions
7310 * You may want to disable this in production for a significant performance boost. See
7311 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7313 * The default value is true.
7315 var debugInfoEnabled = true;
7316 this.debugInfoEnabled = function(enabled) {
7317 if (isDefined(enabled)) {
7318 debugInfoEnabled = enabled;
7321 return debugInfoEnabled;
7325 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7326 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7327 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7328 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7330 var Attributes = function(element, attributesToCopy) {
7331 if (attributesToCopy) {
7332 var keys = Object.keys(attributesToCopy);
7335 for (i = 0, l = keys.length; i < l; i++) {
7337 this[key] = attributesToCopy[key];
7343 this.$$element = element;
7346 Attributes.prototype = {
7349 * @name $compile.directive.Attributes#$normalize
7353 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7354 * `data-`) to its normalized, camelCase form.
7356 * Also there is special case for Moz prefix starting with upper case letter.
7358 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7360 * @param {string} name Name to normalize
7362 $normalize: directiveNormalize,
7367 * @name $compile.directive.Attributes#$addClass
7371 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7372 * are enabled then an animation will be triggered for the class addition.
7374 * @param {string} classVal The className value that will be added to the element
7376 $addClass: function(classVal) {
7377 if (classVal && classVal.length > 0) {
7378 $animate.addClass(this.$$element, classVal);
7384 * @name $compile.directive.Attributes#$removeClass
7388 * Removes the CSS class value specified by the classVal parameter from the element. If
7389 * animations are enabled then an animation will be triggered for the class removal.
7391 * @param {string} classVal The className value that will be removed from the element
7393 $removeClass: function(classVal) {
7394 if (classVal && classVal.length > 0) {
7395 $animate.removeClass(this.$$element, classVal);
7401 * @name $compile.directive.Attributes#$updateClass
7405 * Adds and removes the appropriate CSS class values to the element based on the difference
7406 * between the new and old CSS class values (specified as newClasses and oldClasses).
7408 * @param {string} newClasses The current CSS className value
7409 * @param {string} oldClasses The former CSS className value
7411 $updateClass: function(newClasses, oldClasses) {
7412 var toAdd = tokenDifference(newClasses, oldClasses);
7413 if (toAdd && toAdd.length) {
7414 $animate.addClass(this.$$element, toAdd);
7417 var toRemove = tokenDifference(oldClasses, newClasses);
7418 if (toRemove && toRemove.length) {
7419 $animate.removeClass(this.$$element, toRemove);
7424 * Set a normalized attribute on the element in a way such that all directives
7425 * can share the attribute. This function properly handles boolean attributes.
7426 * @param {string} key Normalized key. (ie ngAttribute)
7427 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7428 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7430 * @param {string=} attrName Optional none normalized name. Defaults to key.
7432 $set: function(key, value, writeAttr, attrName) {
7433 // TODO: decide whether or not to throw an error if "class"
7434 //is set through this function since it may cause $updateClass to
7437 var node = this.$$element[0],
7438 booleanKey = getBooleanAttrName(node, key),
7439 aliasedKey = getAliasedAttrName(key),
7444 this.$$element.prop(key, value);
7445 attrName = booleanKey;
7446 } else if (aliasedKey) {
7447 this[aliasedKey] = value;
7448 observer = aliasedKey;
7453 // translate normalized key to actual key
7455 this.$attr[key] = attrName;
7457 attrName = this.$attr[key];
7459 this.$attr[key] = attrName = snake_case(key, '-');
7463 nodeName = nodeName_(this.$$element);
7465 if ((nodeName === 'a' && key === 'href') ||
7466 (nodeName === 'img' && key === 'src')) {
7467 // sanitize a[href] and img[src] values
7468 this[key] = value = $$sanitizeUri(value, key === 'src');
7469 } else if (nodeName === 'img' && key === 'srcset') {
7470 // sanitize img[srcset] values
7473 // first check if there are spaces because it's not the same pattern
7474 var trimmedSrcset = trim(value);
7475 // ( 999x ,| 999w ,| ,|, )
7476 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7477 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7479 // split srcset into tuple of uri and descriptor except for the last item
7480 var rawUris = trimmedSrcset.split(pattern);
7483 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7484 for (var i = 0; i < nbrUrisWith2parts; i++) {
7485 var innerIdx = i * 2;
7487 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7488 // add the descriptor
7489 result += (" " + trim(rawUris[innerIdx + 1]));
7492 // split the last item into uri and descriptor
7493 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7495 // sanitize the last uri
7496 result += $$sanitizeUri(trim(lastTuple[0]), true);
7498 // and add the last descriptor if any
7499 if (lastTuple.length === 2) {
7500 result += (" " + trim(lastTuple[1]));
7502 this[key] = value = result;
7505 if (writeAttr !== false) {
7506 if (value === null || isUndefined(value)) {
7507 this.$$element.removeAttr(attrName);
7509 this.$$element.attr(attrName, value);
7514 var $$observers = this.$$observers;
7515 $$observers && forEach($$observers[observer], function(fn) {
7519 $exceptionHandler(e);
7527 * @name $compile.directive.Attributes#$observe
7531 * Observes an interpolated attribute.
7533 * The observer function will be invoked once during the next `$digest` following
7534 * compilation. The observer is then invoked whenever the interpolated value
7537 * @param {string} key Normalized key. (ie ngAttribute) .
7538 * @param {function(interpolatedValue)} fn Function that will be called whenever
7539 the interpolated value of the attribute changes.
7540 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7541 * @returns {function()} Returns a deregistration function for this observer.
7543 $observe: function(key, fn) {
7545 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7546 listeners = ($$observers[key] || ($$observers[key] = []));
7549 $rootScope.$evalAsync(function() {
7550 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7551 // no one registered attribute interpolation function, so lets call it manually
7557 arrayRemove(listeners, fn);
7563 function safeAddClass($element, className) {
7565 $element.addClass(className);
7567 // ignore, since it means that we are trying to set class on
7568 // SVG element, where class name is read-only.
7573 var startSymbol = $interpolate.startSymbol(),
7574 endSymbol = $interpolate.endSymbol(),
7575 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7577 : function denormalizeTemplate(template) {
7578 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7580 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7581 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7583 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7584 var bindings = $element.data('$binding') || [];
7586 if (isArray(binding)) {
7587 bindings = bindings.concat(binding);
7589 bindings.push(binding);
7592 $element.data('$binding', bindings);
7595 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7596 safeAddClass($element, 'ng-binding');
7599 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7600 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7601 $element.data(dataName, scope);
7604 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7605 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7610 //================================
7612 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7613 previousCompileContext) {
7614 if (!($compileNodes instanceof jqLite)) {
7615 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7617 $compileNodes = jqLite($compileNodes);
7619 // We can not compile top level text elements since text nodes can be merged and we will
7620 // not be able to attach scope data to them, so we will wrap them in <span>
7621 forEach($compileNodes, function(node, index) {
7622 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7623 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7626 var compositeLinkFn =
7627 compileNodes($compileNodes, transcludeFn, $compileNodes,
7628 maxPriority, ignoreDirective, previousCompileContext);
7629 compile.$$addScopeClass($compileNodes);
7630 var namespace = null;
7631 return function publicLinkFn(scope, cloneConnectFn, options) {
7632 assertArg(scope, 'scope');
7634 if (previousCompileContext && previousCompileContext.needsNewScope) {
7635 // A parent directive did a replace and a directive on this element asked
7636 // for transclusion, which caused us to lose a layer of element on which
7637 // we could hold the new transclusion scope, so we will create it manually
7639 scope = scope.$parent.$new();
7642 options = options || {};
7643 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7644 transcludeControllers = options.transcludeControllers,
7645 futureParentElement = options.futureParentElement;
7647 // When `parentBoundTranscludeFn` is passed, it is a
7648 // `controllersBoundTransclude` function (it was previously passed
7649 // as `transclude` to directive.link) so we must unwrap it to get
7650 // its `boundTranscludeFn`
7651 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7652 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7656 namespace = detectNamespaceForChildElements(futureParentElement);
7659 if (namespace !== 'html') {
7660 // When using a directive with replace:true and templateUrl the $compileNodes
7661 // (or a child element inside of them)
7662 // might change, so we need to recreate the namespace adapted compileNodes
7663 // for call to the link function.
7664 // Note: This will already clone the nodes...
7666 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7668 } else if (cloneConnectFn) {
7669 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7670 // and sometimes changes the structure of the DOM.
7671 $linkNode = JQLitePrototype.clone.call($compileNodes);
7673 $linkNode = $compileNodes;
7676 if (transcludeControllers) {
7677 for (var controllerName in transcludeControllers) {
7678 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7682 compile.$$addScopeInfo($linkNode, scope);
7684 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7685 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7690 function detectNamespaceForChildElements(parentElement) {
7691 // TODO: Make this detect MathML as well...
7692 var node = parentElement && parentElement[0];
7696 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7701 * Compile function matches each node in nodeList against the directives. Once all directives
7702 * for a particular node are collected their compile functions are executed. The compile
7703 * functions return values - the linking functions - are combined into a composite linking
7704 * function, which is the a linking function for the node.
7706 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7707 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7708 * scope argument is auto-generated to the new child of the transcluded parent scope.
7709 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7710 * the rootElement must be set the jqLite collection of the compile root. This is
7711 * needed so that the jqLite collection items can be replaced with widgets.
7712 * @param {number=} maxPriority Max directive priority.
7713 * @returns {Function} A composite linking function of all of the matched directives or null.
7715 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7716 previousCompileContext) {
7718 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7720 for (var i = 0; i < nodeList.length; i++) {
7721 attrs = new Attributes();
7723 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7724 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7727 nodeLinkFn = (directives.length)
7728 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7729 null, [], [], previousCompileContext)
7732 if (nodeLinkFn && nodeLinkFn.scope) {
7733 compile.$$addScopeClass(attrs.$$element);
7736 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7737 !(childNodes = nodeList[i].childNodes) ||
7740 : compileNodes(childNodes,
7742 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7743 && nodeLinkFn.transclude) : transcludeFn);
7745 if (nodeLinkFn || childLinkFn) {
7746 linkFns.push(i, nodeLinkFn, childLinkFn);
7748 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7751 //use the previous context only for the first element in the virtual group
7752 previousCompileContext = null;
7755 // return a linking function if we have found anything, null otherwise
7756 return linkFnFound ? compositeLinkFn : null;
7758 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7759 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7763 if (nodeLinkFnFound) {
7764 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7765 // offsets don't get screwed up
7766 var nodeListLength = nodeList.length;
7767 stableNodeList = new Array(nodeListLength);
7769 // create a sparse array by only copying the elements which have a linkFn
7770 for (i = 0; i < linkFns.length; i+=3) {
7772 stableNodeList[idx] = nodeList[idx];
7775 stableNodeList = nodeList;
7778 for (i = 0, ii = linkFns.length; i < ii;) {
7779 node = stableNodeList[linkFns[i++]];
7780 nodeLinkFn = linkFns[i++];
7781 childLinkFn = linkFns[i++];
7784 if (nodeLinkFn.scope) {
7785 childScope = scope.$new();
7786 compile.$$addScopeInfo(jqLite(node), childScope);
7791 if (nodeLinkFn.transcludeOnThisElement) {
7792 childBoundTranscludeFn = createBoundTranscludeFn(
7793 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7795 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7796 childBoundTranscludeFn = parentBoundTranscludeFn;
7798 } else if (!parentBoundTranscludeFn && transcludeFn) {
7799 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7802 childBoundTranscludeFn = null;
7805 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7807 } else if (childLinkFn) {
7808 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7814 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7816 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7818 if (!transcludedScope) {
7819 transcludedScope = scope.$new(false, containingScope);
7820 transcludedScope.$$transcluded = true;
7823 return transcludeFn(transcludedScope, cloneFn, {
7824 parentBoundTranscludeFn: previousBoundTranscludeFn,
7825 transcludeControllers: controllers,
7826 futureParentElement: futureParentElement
7830 return boundTranscludeFn;
7834 * Looks for directives on the given node and adds them to the directive collection which is
7837 * @param node Node to search.
7838 * @param directives An array to which the directives are added to. This array is sorted before
7839 * the function returns.
7840 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7841 * @param {number=} maxPriority Max directive priority.
7843 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7844 var nodeType = node.nodeType,
7845 attrsMap = attrs.$attr,
7850 case NODE_TYPE_ELEMENT: /* Element */
7851 // use the node name: <directive>
7852 addDirective(directives,
7853 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7855 // iterate over the attributes
7856 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7857 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7858 var attrStartName = false;
7859 var attrEndName = false;
7863 value = trim(attr.value);
7865 // support ngAttr attribute binding
7866 ngAttrName = directiveNormalize(name);
7867 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7868 name = name.replace(PREFIX_REGEXP, '')
7869 .substr(8).replace(/_(.)/g, function(match, letter) {
7870 return letter.toUpperCase();
7874 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7875 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7876 attrStartName = name;
7877 attrEndName = name.substr(0, name.length - 5) + 'end';
7878 name = name.substr(0, name.length - 6);
7881 nName = directiveNormalize(name.toLowerCase());
7882 attrsMap[nName] = name;
7883 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7884 attrs[nName] = value;
7885 if (getBooleanAttrName(node, nName)) {
7886 attrs[nName] = true; // presence means true
7889 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7890 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7894 // use class as directive
7895 className = node.className;
7896 if (isObject(className)) {
7897 // Maybe SVGAnimatedString
7898 className = className.animVal;
7900 if (isString(className) && className !== '') {
7901 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7902 nName = directiveNormalize(match[2]);
7903 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7904 attrs[nName] = trim(match[3]);
7906 className = className.substr(match.index + match[0].length);
7910 case NODE_TYPE_TEXT: /* Text Node */
7912 // Workaround for #11781
7913 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7914 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7915 node.parentNode.removeChild(node.nextSibling);
7918 addTextInterpolateDirective(directives, node.nodeValue);
7920 case NODE_TYPE_COMMENT: /* Comment */
7922 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7924 nName = directiveNormalize(match[1]);
7925 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7926 attrs[nName] = trim(match[2]);
7930 // turns out that under some circumstances IE9 throws errors when one attempts to read
7931 // comment's node value.
7932 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7937 directives.sort(byPriority);
7942 * Given a node with an directive-start it collects all of the siblings until it finds
7949 function groupScan(node, attrStart, attrEnd) {
7952 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7955 throw $compileMinErr('uterdir',
7956 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7957 attrStart, attrEnd);
7959 if (node.nodeType == NODE_TYPE_ELEMENT) {
7960 if (node.hasAttribute(attrStart)) depth++;
7961 if (node.hasAttribute(attrEnd)) depth--;
7964 node = node.nextSibling;
7965 } while (depth > 0);
7970 return jqLite(nodes);
7974 * Wrapper for linking function which converts normal linking function into a grouped
7979 * @returns {Function}
7981 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7982 return function(scope, element, attrs, controllers, transcludeFn) {
7983 element = groupScan(element[0], attrStart, attrEnd);
7984 return linkFn(scope, element, attrs, controllers, transcludeFn);
7989 * Once the directives have been collected, their compile functions are executed. This method
7990 * is responsible for inlining directive templates as well as terminating the application
7991 * of the directives if the terminal directive has been reached.
7993 * @param {Array} directives Array of collected directives to execute their compile function.
7994 * this needs to be pre-sorted by priority order.
7995 * @param {Node} compileNode The raw DOM node to apply the compile functions to
7996 * @param {Object} templateAttrs The shared attribute function
7997 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7998 * scope argument is auto-generated to the new
7999 * child of the transcluded parent scope.
8000 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
8001 * argument has the root jqLite array so that we can replace nodes
8003 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
8004 * compiling the transclusion.
8005 * @param {Array.<Function>} preLinkFns
8006 * @param {Array.<Function>} postLinkFns
8007 * @param {Object} previousCompileContext Context used for previous compilation of the current
8009 * @returns {Function} linkFn
8011 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
8012 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
8013 previousCompileContext) {
8014 previousCompileContext = previousCompileContext || {};
8016 var terminalPriority = -Number.MAX_VALUE,
8017 newScopeDirective = previousCompileContext.newScopeDirective,
8018 controllerDirectives = previousCompileContext.controllerDirectives,
8019 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
8020 templateDirective = previousCompileContext.templateDirective,
8021 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
8022 hasTranscludeDirective = false,
8023 hasTemplate = false,
8024 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
8025 $compileNode = templateAttrs.$$element = jqLite(compileNode),
8029 replaceDirective = originalReplaceDirective,
8030 childTranscludeFn = transcludeFn,
8034 // executes all directives on the current element
8035 for (var i = 0, ii = directives.length; i < ii; i++) {
8036 directive = directives[i];
8037 var attrStart = directive.$$start;
8038 var attrEnd = directive.$$end;
8040 // collect multiblock sections
8042 $compileNode = groupScan(compileNode, attrStart, attrEnd);
8044 $template = undefined;
8046 if (terminalPriority > directive.priority) {
8047 break; // prevent further processing of directives
8050 if (directiveValue = directive.scope) {
8052 // skip the check for directives with async templates, we'll check the derived sync
8053 // directive when the template arrives
8054 if (!directive.templateUrl) {
8055 if (isObject(directiveValue)) {
8056 // This directive is trying to add an isolated scope.
8057 // Check that there is no scope of any kind already
8058 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
8059 directive, $compileNode);
8060 newIsolateScopeDirective = directive;
8062 // This directive is trying to add a child scope.
8063 // Check that there is no isolated scope already
8064 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
8069 newScopeDirective = newScopeDirective || directive;
8072 directiveName = directive.name;
8074 if (!directive.templateUrl && directive.controller) {
8075 directiveValue = directive.controller;
8076 controllerDirectives = controllerDirectives || createMap();
8077 assertNoDuplicate("'" + directiveName + "' controller",
8078 controllerDirectives[directiveName], directive, $compileNode);
8079 controllerDirectives[directiveName] = directive;
8082 if (directiveValue = directive.transclude) {
8083 hasTranscludeDirective = true;
8085 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8086 // This option should only be used by directives that know how to safely handle element transclusion,
8087 // where the transcluded nodes are added or replaced after linking.
8088 if (!directive.$$tlb) {
8089 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8090 nonTlbTranscludeDirective = directive;
8093 if (directiveValue == 'element') {
8094 hasElementTranscludeDirective = true;
8095 terminalPriority = directive.priority;
8096 $template = $compileNode;
8097 $compileNode = templateAttrs.$$element =
8098 jqLite(document.createComment(' ' + directiveName + ': ' +
8099 templateAttrs[directiveName] + ' '));
8100 compileNode = $compileNode[0];
8101 replaceWith(jqCollection, sliceArgs($template), compileNode);
8103 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8104 replaceDirective && replaceDirective.name, {
8106 // - controllerDirectives - otherwise we'll create duplicates controllers
8107 // - newIsolateScopeDirective or templateDirective - combining templates with
8108 // element transclusion doesn't make sense.
8110 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8111 // on the same element more than once.
8112 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8115 $template = jqLite(jqLiteClone(compileNode)).contents();
8116 $compileNode.empty(); // clear contents
8117 childTranscludeFn = compile($template, transcludeFn, undefined,
8118 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8122 if (directive.template) {
8124 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8125 templateDirective = directive;
8127 directiveValue = (isFunction(directive.template))
8128 ? directive.template($compileNode, templateAttrs)
8129 : directive.template;
8131 directiveValue = denormalizeTemplate(directiveValue);
8133 if (directive.replace) {
8134 replaceDirective = directive;
8135 if (jqLiteIsTextNode(directiveValue)) {
8138 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8140 compileNode = $template[0];
8142 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8143 throw $compileMinErr('tplrt',
8144 "Template for directive '{0}' must have exactly one root element. {1}",
8148 replaceWith(jqCollection, $compileNode, compileNode);
8150 var newTemplateAttrs = {$attr: {}};
8152 // combine directives from the original node and from the template:
8153 // - take the array of directives for this element
8154 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8155 // - collect directives from the template and sort them by priority
8156 // - combine directives as: processed + template + unprocessed
8157 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8158 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8160 if (newIsolateScopeDirective || newScopeDirective) {
8161 // The original directive caused the current element to be replaced but this element
8162 // also needs to have a new scope, so we need to tell the template directives
8163 // that they would need to get their scope from further up, if they require transclusion
8164 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8166 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8167 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8169 ii = directives.length;
8171 $compileNode.html(directiveValue);
8175 if (directive.templateUrl) {
8177 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8178 templateDirective = directive;
8180 if (directive.replace) {
8181 replaceDirective = directive;
8184 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8185 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8186 controllerDirectives: controllerDirectives,
8187 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8188 newIsolateScopeDirective: newIsolateScopeDirective,
8189 templateDirective: templateDirective,
8190 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8192 ii = directives.length;
8193 } else if (directive.compile) {
8195 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8196 if (isFunction(linkFn)) {
8197 addLinkFns(null, linkFn, attrStart, attrEnd);
8198 } else if (linkFn) {
8199 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8202 $exceptionHandler(e, startingTag($compileNode));
8206 if (directive.terminal) {
8207 nodeLinkFn.terminal = true;
8208 terminalPriority = Math.max(terminalPriority, directive.priority);
8213 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8214 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8215 nodeLinkFn.templateOnThisElement = hasTemplate;
8216 nodeLinkFn.transclude = childTranscludeFn;
8218 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8220 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8223 ////////////////////
8225 function addLinkFns(pre, post, attrStart, attrEnd) {
8227 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8228 pre.require = directive.require;
8229 pre.directiveName = directiveName;
8230 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8231 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8233 preLinkFns.push(pre);
8236 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8237 post.require = directive.require;
8238 post.directiveName = directiveName;
8239 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8240 post = cloneAndAnnotateFn(post, {isolateScope: true});
8242 postLinkFns.push(post);
8247 function getControllers(directiveName, require, $element, elementControllers) {
8250 if (isString(require)) {
8251 var match = require.match(REQUIRE_PREFIX_REGEXP);
8252 var name = require.substring(match[0].length);
8253 var inheritType = match[1] || match[3];
8254 var optional = match[2] === '?';
8256 //If only parents then start at the parent element
8257 if (inheritType === '^^') {
8258 $element = $element.parent();
8259 //Otherwise attempt getting the controller from elementControllers in case
8260 //the element is transcluded (and has no data) and to avoid .data if possible
8262 value = elementControllers && elementControllers[name];
8263 value = value && value.instance;
8267 var dataName = '$' + name + 'Controller';
8268 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8271 if (!value && !optional) {
8272 throw $compileMinErr('ctreq',
8273 "Controller '{0}', required by directive '{1}', can't be found!",
8274 name, directiveName);
8276 } else if (isArray(require)) {
8278 for (var i = 0, ii = require.length; i < ii; i++) {
8279 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8283 return value || null;
8286 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8287 var elementControllers = createMap();
8288 for (var controllerKey in controllerDirectives) {
8289 var directive = controllerDirectives[controllerKey];
8291 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8294 $transclude: transcludeFn
8297 var controller = directive.controller;
8298 if (controller == '@') {
8299 controller = attrs[directive.name];
8302 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8304 // For directives with element transclusion the element is a comment,
8305 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8306 // clean up (http://bugs.jquery.com/ticket/8335).
8307 // Instead, we save the controllers for the element in a local hash and attach to .data
8308 // later, once we have the actual element.
8309 elementControllers[directive.name] = controllerInstance;
8310 if (!hasElementTranscludeDirective) {
8311 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8314 return elementControllers;
8317 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8318 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8319 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8321 if (compileNode === linkNode) {
8322 attrs = templateAttrs;
8323 $element = templateAttrs.$$element;
8325 $element = jqLite(linkNode);
8326 attrs = new Attributes($element, templateAttrs);
8329 controllerScope = scope;
8330 if (newIsolateScopeDirective) {
8331 isolateScope = scope.$new(true);
8332 } else if (newScopeDirective) {
8333 controllerScope = scope.$parent;
8336 if (boundTranscludeFn) {
8337 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8338 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8339 transcludeFn = controllersBoundTransclude;
8340 transcludeFn.$$boundTransclude = boundTranscludeFn;
8343 if (controllerDirectives) {
8344 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8347 if (newIsolateScopeDirective) {
8348 // Initialize isolate scope bindings for new isolate scope directive.
8349 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8350 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8351 compile.$$addScopeClass($element, true);
8352 isolateScope.$$isolateBindings =
8353 newIsolateScopeDirective.$$isolateBindings;
8354 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8355 isolateScope.$$isolateBindings,
8356 newIsolateScopeDirective);
8357 if (removeScopeBindingWatches) {
8358 isolateScope.$on('$destroy', removeScopeBindingWatches);
8362 // Initialize bindToController bindings
8363 for (var name in elementControllers) {
8364 var controllerDirective = controllerDirectives[name];
8365 var controller = elementControllers[name];
8366 var bindings = controllerDirective.$$bindings.bindToController;
8368 if (controller.identifier && bindings) {
8369 removeControllerBindingWatches =
8370 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8373 var controllerResult = controller();
8374 if (controllerResult !== controller.instance) {
8375 // If the controller constructor has a return value, overwrite the instance
8376 // from setupControllers
8377 controller.instance = controllerResult;
8378 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8379 removeControllerBindingWatches && removeControllerBindingWatches();
8380 removeControllerBindingWatches =
8381 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8386 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8387 linkFn = preLinkFns[i];
8388 invokeLinkFn(linkFn,
8389 linkFn.isolateScope ? isolateScope : scope,
8392 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8398 // We only pass the isolate scope, if the isolate directive has a template,
8399 // otherwise the child elements do not belong to the isolate directive.
8400 var scopeToChild = scope;
8401 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8402 scopeToChild = isolateScope;
8404 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8407 for (i = postLinkFns.length - 1; i >= 0; i--) {
8408 linkFn = postLinkFns[i];
8409 invokeLinkFn(linkFn,
8410 linkFn.isolateScope ? isolateScope : scope,
8413 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8418 // This is the function that is injected as `$transclude`.
8419 // Note: all arguments are optional!
8420 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8421 var transcludeControllers;
8423 // No scope passed in:
8424 if (!isScope(scope)) {
8425 futureParentElement = cloneAttachFn;
8426 cloneAttachFn = scope;
8430 if (hasElementTranscludeDirective) {
8431 transcludeControllers = elementControllers;
8433 if (!futureParentElement) {
8434 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8436 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8441 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8442 // or child scope created. For instance:
8443 // * if the directive has been pulled into a template because another directive with a higher priority
8444 // asked for element transclusion
8445 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8446 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8447 function markDirectiveScope(directives, isolateScope, newScope) {
8448 for (var j = 0, jj = directives.length; j < jj; j++) {
8449 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8454 * looks up the directive and decorates it with exception handling and proper parameters. We
8455 * call this the boundDirective.
8457 * @param {string} name name of the directive to look up.
8458 * @param {string} location The directive must be found in specific format.
8459 * String containing any of theses characters:
8461 * * `E`: element name
8465 * @returns {boolean} true if directive was added.
8467 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8469 if (name === ignoreDirective) return null;
8471 if (hasDirectives.hasOwnProperty(name)) {
8472 for (var directive, directives = $injector.get(name + Suffix),
8473 i = 0, ii = directives.length; i < ii; i++) {
8475 directive = directives[i];
8476 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8477 directive.restrict.indexOf(location) != -1) {
8478 if (startAttrName) {
8479 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8481 tDirectives.push(directive);
8484 } catch (e) { $exceptionHandler(e); }
8492 * looks up the directive and returns true if it is a multi-element directive,
8493 * and therefore requires DOM nodes between -start and -end markers to be grouped
8496 * @param {string} name name of the directive to look up.
8497 * @returns true if directive was registered as multi-element.
8499 function directiveIsMultiElement(name) {
8500 if (hasDirectives.hasOwnProperty(name)) {
8501 for (var directive, directives = $injector.get(name + Suffix),
8502 i = 0, ii = directives.length; i < ii; i++) {
8503 directive = directives[i];
8504 if (directive.multiElement) {
8513 * When the element is replaced with HTML template then the new attributes
8514 * on the template need to be merged with the existing attributes in the DOM.
8515 * The desired effect is to have both of the attributes present.
8517 * @param {object} dst destination attributes (original DOM)
8518 * @param {object} src source attributes (from the directive template)
8520 function mergeTemplateAttributes(dst, src) {
8521 var srcAttr = src.$attr,
8522 dstAttr = dst.$attr,
8523 $element = dst.$$element;
8525 // reapply the old attributes to the new element
8526 forEach(dst, function(value, key) {
8527 if (key.charAt(0) != '$') {
8528 if (src[key] && src[key] !== value) {
8529 value += (key === 'style' ? ';' : ' ') + src[key];
8531 dst.$set(key, value, true, srcAttr[key]);
8535 // copy the new attributes on the old attrs object
8536 forEach(src, function(value, key) {
8537 if (key == 'class') {
8538 safeAddClass($element, value);
8539 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8540 } else if (key == 'style') {
8541 $element.attr('style', $element.attr('style') + ';' + value);
8542 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8543 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8544 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8545 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8546 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8548 dstAttr[key] = srcAttr[key];
8554 function compileTemplateUrl(directives, $compileNode, tAttrs,
8555 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8557 afterTemplateNodeLinkFn,
8558 afterTemplateChildLinkFn,
8559 beforeTemplateCompileNode = $compileNode[0],
8560 origAsyncDirective = directives.shift(),
8561 derivedSyncDirective = inherit(origAsyncDirective, {
8562 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8564 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8565 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8566 : origAsyncDirective.templateUrl,
8567 templateNamespace = origAsyncDirective.templateNamespace;
8569 $compileNode.empty();
8571 $templateRequest(templateUrl)
8572 .then(function(content) {
8573 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8575 content = denormalizeTemplate(content);
8577 if (origAsyncDirective.replace) {
8578 if (jqLiteIsTextNode(content)) {
8581 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8583 compileNode = $template[0];
8585 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8586 throw $compileMinErr('tplrt',
8587 "Template for directive '{0}' must have exactly one root element. {1}",
8588 origAsyncDirective.name, templateUrl);
8591 tempTemplateAttrs = {$attr: {}};
8592 replaceWith($rootElement, $compileNode, compileNode);
8593 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8595 if (isObject(origAsyncDirective.scope)) {
8596 // the original directive that caused the template to be loaded async required
8598 markDirectiveScope(templateDirectives, true);
8600 directives = templateDirectives.concat(directives);
8601 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8603 compileNode = beforeTemplateCompileNode;
8604 $compileNode.html(content);
8607 directives.unshift(derivedSyncDirective);
8609 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8610 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8611 previousCompileContext);
8612 forEach($rootElement, function(node, i) {
8613 if (node == compileNode) {
8614 $rootElement[i] = $compileNode[0];
8617 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8619 while (linkQueue.length) {
8620 var scope = linkQueue.shift(),
8621 beforeTemplateLinkNode = linkQueue.shift(),
8622 linkRootElement = linkQueue.shift(),
8623 boundTranscludeFn = linkQueue.shift(),
8624 linkNode = $compileNode[0];
8626 if (scope.$$destroyed) continue;
8628 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8629 var oldClasses = beforeTemplateLinkNode.className;
8631 if (!(previousCompileContext.hasElementTranscludeDirective &&
8632 origAsyncDirective.replace)) {
8633 // it was cloned therefore we have to clone as well.
8634 linkNode = jqLiteClone(compileNode);
8636 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8638 // Copy in CSS classes from original node
8639 safeAddClass(jqLite(linkNode), oldClasses);
8641 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8642 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8644 childBoundTranscludeFn = boundTranscludeFn;
8646 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8647 childBoundTranscludeFn);
8652 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8653 var childBoundTranscludeFn = boundTranscludeFn;
8654 if (scope.$$destroyed) return;
8656 linkQueue.push(scope,
8659 childBoundTranscludeFn);
8661 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8662 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8664 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8671 * Sorting function for bound directives.
8673 function byPriority(a, b) {
8674 var diff = b.priority - a.priority;
8675 if (diff !== 0) return diff;
8676 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8677 return a.index - b.index;
8680 function assertNoDuplicate(what, previousDirective, directive, element) {
8682 function wrapModuleNameIfDefined(moduleName) {
8684 (' (module: ' + moduleName + ')') :
8688 if (previousDirective) {
8689 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8690 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8691 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8696 function addTextInterpolateDirective(directives, text) {
8697 var interpolateFn = $interpolate(text, true);
8698 if (interpolateFn) {
8701 compile: function textInterpolateCompileFn(templateNode) {
8702 var templateNodeParent = templateNode.parent(),
8703 hasCompileParent = !!templateNodeParent.length;
8705 // When transcluding a template that has bindings in the root
8706 // we don't have a parent and thus need to add the class during linking fn.
8707 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8709 return function textInterpolateLinkFn(scope, node) {
8710 var parent = node.parent();
8711 if (!hasCompileParent) compile.$$addBindingClass(parent);
8712 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8713 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8714 node[0].nodeValue = value;
8723 function wrapTemplate(type, template) {
8724 type = lowercase(type || 'html');
8728 var wrapper = document.createElement('div');
8729 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8730 return wrapper.childNodes[0].childNodes;
8737 function getTrustedContext(node, attrNormalizedName) {
8738 if (attrNormalizedName == "srcdoc") {
8741 var tag = nodeName_(node);
8742 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8743 if (attrNormalizedName == "xlinkHref" ||
8744 (tag == "form" && attrNormalizedName == "action") ||
8745 (tag != "img" && (attrNormalizedName == "src" ||
8746 attrNormalizedName == "ngSrc"))) {
8747 return $sce.RESOURCE_URL;
8752 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8753 var trustedContext = getTrustedContext(node, name);
8754 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8756 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8758 // no interpolation found -> ignore
8759 if (!interpolateFn) return;
8762 if (name === "multiple" && nodeName_(node) === "select") {
8763 throw $compileMinErr("selmulti",
8764 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8770 compile: function() {
8772 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8773 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8775 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8776 throw $compileMinErr('nodomevents',
8777 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8778 "ng- versions (such as ng-click instead of onclick) instead.");
8781 // If the attribute has changed since last $interpolate()ed
8782 var newValue = attr[name];
8783 if (newValue !== value) {
8784 // we need to interpolate again since the attribute value has been updated
8785 // (e.g. by another directive's compile function)
8786 // ensure unset/empty values make interpolateFn falsy
8787 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8791 // if attribute was updated so that there is no interpolation going on we don't want to
8792 // register any observers
8793 if (!interpolateFn) return;
8795 // initialize attr object so that it's ready in case we need the value for isolate
8796 // scope initialization, otherwise the value would not be available from isolate
8797 // directive's linking fn during linking phase
8798 attr[name] = interpolateFn(scope);
8800 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8801 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8802 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8803 //special case for class attribute addition + removal
8804 //so that class changes can tap into the animation
8805 //hooks provided by the $animate service. Be sure to
8806 //skip animations when the first digest occurs (when
8807 //both the new and the old values are the same) since
8808 //the CSS classes are the non-interpolated values
8809 if (name === 'class' && newValue != oldValue) {
8810 attr.$updateClass(newValue, oldValue);
8812 attr.$set(name, newValue);
8823 * This is a special jqLite.replaceWith, which can replace items which
8824 * have no parents, provided that the containing jqLite collection is provided.
8826 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8827 * in the root of the tree.
8828 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8829 * the shell, but replace its DOM node reference.
8830 * @param {Node} newNode The new DOM node.
8832 function replaceWith($rootElement, elementsToRemove, newNode) {
8833 var firstElementToRemove = elementsToRemove[0],
8834 removeCount = elementsToRemove.length,
8835 parent = firstElementToRemove.parentNode,
8839 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8840 if ($rootElement[i] == firstElementToRemove) {
8841 $rootElement[i++] = newNode;
8842 for (var j = i, j2 = j + removeCount - 1,
8843 jj = $rootElement.length;
8844 j < jj; j++, j2++) {
8846 $rootElement[j] = $rootElement[j2];
8848 delete $rootElement[j];
8851 $rootElement.length -= removeCount - 1;
8853 // If the replaced element is also the jQuery .context then replace it
8854 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8855 // http://api.jquery.com/context/
8856 if ($rootElement.context === firstElementToRemove) {
8857 $rootElement.context = newNode;
8865 parent.replaceChild(newNode, firstElementToRemove);
8868 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8869 var fragment = document.createDocumentFragment();
8870 fragment.appendChild(firstElementToRemove);
8872 if (jqLite.hasData(firstElementToRemove)) {
8873 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8874 // data here because there's no public interface in jQuery to do that and copying over
8875 // event listeners (which is the main use of private data) wouldn't work anyway.
8876 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8878 // Remove data of the replaced element. We cannot just call .remove()
8879 // on the element it since that would deallocate scope that is needed
8880 // for the new node. Instead, remove the data "manually".
8882 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8884 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8885 // the replaced element. The cleanData version monkey-patched by Angular would cause
8886 // the scope to be trashed and we do need the very same scope to work with the new
8887 // element. However, we cannot just cache the non-patched version and use it here as
8888 // that would break if another library patches the method after Angular does (one
8889 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8890 // skipped this one time.
8891 skipDestroyOnNextJQueryCleanData = true;
8892 jQuery.cleanData([firstElementToRemove]);
8896 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8897 var element = elementsToRemove[k];
8898 jqLite(element).remove(); // must do this way to clean up expando
8899 fragment.appendChild(element);
8900 delete elementsToRemove[k];
8903 elementsToRemove[0] = newNode;
8904 elementsToRemove.length = 1;
8908 function cloneAndAnnotateFn(fn, annotation) {
8909 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8913 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8915 linkFn(scope, $element, attrs, controllers, transcludeFn);
8917 $exceptionHandler(e, startingTag($element));
8922 // Set up $watches for isolate scope and controller bindings. This process
8923 // only occurs for isolate scopes and new scopes with controllerAs.
8924 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8925 var removeWatchCollection = [];
8926 forEach(bindings, function(definition, scopeName) {
8927 var attrName = definition.attrName,
8928 optional = definition.optional,
8929 mode = definition.mode, // @, =, or &
8931 parentGet, parentSet, compare;
8936 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8937 destination[scopeName] = attrs[attrName] = void 0;
8939 attrs.$observe(attrName, function(value) {
8940 if (isString(value)) {
8941 destination[scopeName] = value;
8944 attrs.$$observers[attrName].$$scope = scope;
8945 if (isString(attrs[attrName])) {
8946 // If the attribute has been provided then we trigger an interpolation to ensure
8947 // the value is there for use in the link fn
8948 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8953 if (!hasOwnProperty.call(attrs, attrName)) {
8954 if (optional) break;
8955 attrs[attrName] = void 0;
8957 if (optional && !attrs[attrName]) break;
8959 parentGet = $parse(attrs[attrName]);
8960 if (parentGet.literal) {
8963 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8965 parentSet = parentGet.assign || function() {
8966 // reset the change, or we will throw this exception on every $digest
8967 lastValue = destination[scopeName] = parentGet(scope);
8968 throw $compileMinErr('nonassign',
8969 "Expression '{0}' used with directive '{1}' is non-assignable!",
8970 attrs[attrName], directive.name);
8972 lastValue = destination[scopeName] = parentGet(scope);
8973 var parentValueWatch = function parentValueWatch(parentValue) {
8974 if (!compare(parentValue, destination[scopeName])) {
8975 // we are out of sync and need to copy
8976 if (!compare(parentValue, lastValue)) {
8977 // parent changed and it has precedence
8978 destination[scopeName] = parentValue;
8980 // if the parent can be assigned then do so
8981 parentSet(scope, parentValue = destination[scopeName]);
8984 return lastValue = parentValue;
8986 parentValueWatch.$stateful = true;
8988 if (definition.collection) {
8989 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8991 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8993 removeWatchCollection.push(removeWatch);
8997 // Don't assign Object.prototype method to scope
8998 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
9000 // Don't assign noop to destination if expression is not valid
9001 if (parentGet === noop && optional) break;
9003 destination[scopeName] = function(locals) {
9004 return parentGet(scope, locals);
9010 return removeWatchCollection.length && function removeWatches() {
9011 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
9012 removeWatchCollection[i]();
9019 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
9021 * Converts all accepted directives format into proper directive name.
9022 * @param name Name to normalize
9024 function directiveNormalize(name) {
9025 return camelCase(name.replace(PREFIX_REGEXP, ''));
9030 * @name $compile.directive.Attributes
9033 * A shared object between directive compile / linking functions which contains normalized DOM
9034 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
9035 * needed since all of these are treated as equivalent in Angular:
9038 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
9044 * @name $compile.directive.Attributes#$attr
9047 * A map of DOM element attribute names to the normalized name. This is
9048 * needed to do reverse lookup from normalized name back to actual name.
9054 * @name $compile.directive.Attributes#$set
9058 * Set DOM element attribute value.
9061 * @param {string} name Normalized element attribute name of the property to modify. The name is
9062 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
9063 * property to the original name.
9064 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
9070 * Closure compiler type information
9073 function nodesetLinkingFn(
9074 /* angular.Scope */ scope,
9075 /* NodeList */ nodeList,
9076 /* Element */ rootElement,
9077 /* function(Function) */ boundTranscludeFn
9080 function directiveLinkingFn(
9081 /* nodesetLinkingFn */ nodesetLinkingFn,
9082 /* angular.Scope */ scope,
9084 /* Element */ rootElement,
9085 /* function(Function) */ boundTranscludeFn
9088 function tokenDifference(str1, str2) {
9090 tokens1 = str1.split(/\s+/),
9091 tokens2 = str2.split(/\s+/);
9094 for (var i = 0; i < tokens1.length; i++) {
9095 var token = tokens1[i];
9096 for (var j = 0; j < tokens2.length; j++) {
9097 if (token == tokens2[j]) continue outer;
9099 values += (values.length > 0 ? ' ' : '') + token;
9104 function removeComments(jqNodes) {
9105 jqNodes = jqLite(jqNodes);
9106 var i = jqNodes.length;
9113 var node = jqNodes[i];
9114 if (node.nodeType === NODE_TYPE_COMMENT) {
9115 splice.call(jqNodes, i, 1);
9121 var $controllerMinErr = minErr('$controller');
9124 var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9125 function identifierForController(controller, ident) {
9126 if (ident && isString(ident)) return ident;
9127 if (isString(controller)) {
9128 var match = CNTRL_REG.exec(controller);
9129 if (match) return match[3];
9136 * @name $controllerProvider
9138 * The {@link ng.$controller $controller service} is used by Angular to create new
9141 * This provider allows controller registration via the
9142 * {@link ng.$controllerProvider#register register} method.
9144 function $ControllerProvider() {
9145 var controllers = {},
9150 * @name $controllerProvider#register
9151 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9152 * the names and the values are the constructors.
9153 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9154 * annotations in the array notation).
9156 this.register = function(name, constructor) {
9157 assertNotHasOwnProperty(name, 'controller');
9158 if (isObject(name)) {
9159 extend(controllers, name);
9161 controllers[name] = constructor;
9167 * @name $controllerProvider#allowGlobals
9168 * @description If called, allows `$controller` to find controller constructors on `window`
9170 this.allowGlobals = function() {
9175 this.$get = ['$injector', '$window', function($injector, $window) {
9180 * @requires $injector
9182 * @param {Function|string} constructor If called with a function then it's considered to be the
9183 * controller constructor function. Otherwise it's considered to be a string which is used
9184 * to retrieve the controller constructor using the following steps:
9186 * * check if a controller with given name is registered via `$controllerProvider`
9187 * * check if evaluating the string on the current scope returns a constructor
9188 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9189 * `window` object (not recommended)
9191 * The string can use the `controller as property` syntax, where the controller instance is published
9192 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9193 * to work correctly.
9195 * @param {Object} locals Injection locals for Controller.
9196 * @return {Object} Instance of given controller.
9199 * `$controller` service is responsible for instantiating controllers.
9201 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9202 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9204 return function(expression, locals, later, ident) {
9206 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9207 // If true, $controller will allocate the object with the correct
9208 // prototype chain, but will not invoke the controller until a returned
9209 // callback is invoked.
9210 // param `ident` --- An optional label which overrides the label parsed from the controller
9211 // expression, if any.
9212 var instance, match, constructor, identifier;
9213 later = later === true;
9214 if (ident && isString(ident)) {
9218 if (isString(expression)) {
9219 match = expression.match(CNTRL_REG);
9221 throw $controllerMinErr('ctrlfmt',
9222 "Badly formed controller string '{0}'. " +
9223 "Must match `__name__ as __id__` or `__name__`.", expression);
9225 constructor = match[1],
9226 identifier = identifier || match[3];
9227 expression = controllers.hasOwnProperty(constructor)
9228 ? controllers[constructor]
9229 : getter(locals.$scope, constructor, true) ||
9230 (globals ? getter($window, constructor, true) : undefined);
9232 assertArgFn(expression, constructor, true);
9236 // Instantiate controller later:
9237 // This machinery is used to create an instance of the object before calling the
9238 // controller's constructor itself.
9240 // This allows properties to be added to the controller before the constructor is
9241 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9243 // This feature is not intended for use by applications, and is thus not documented
9245 // Object creation: http://jsperf.com/create-constructor/2
9246 var controllerPrototype = (isArray(expression) ?
9247 expression[expression.length - 1] : expression).prototype;
9248 instance = Object.create(controllerPrototype || null);
9251 addIdentifier(locals, identifier, instance, constructor || expression.name);
9255 return instantiate = extend(function() {
9256 var result = $injector.invoke(expression, instance, locals, constructor);
9257 if (result !== instance && (isObject(result) || isFunction(result))) {
9260 // If result changed, re-assign controllerAs value to scope.
9261 addIdentifier(locals, identifier, instance, constructor || expression.name);
9267 identifier: identifier
9271 instance = $injector.instantiate(expression, locals, constructor);
9274 addIdentifier(locals, identifier, instance, constructor || expression.name);
9280 function addIdentifier(locals, identifier, instance, name) {
9281 if (!(locals && isObject(locals.$scope))) {
9282 throw minErr('$controller')('noscp',
9283 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9287 locals.$scope[identifier] = instance;
9298 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9301 <example module="documentExample">
9302 <file name="index.html">
9303 <div ng-controller="ExampleController">
9304 <p>$document title: <b ng-bind="title"></b></p>
9305 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9308 <file name="script.js">
9309 angular.module('documentExample', [])
9310 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9311 $scope.title = $document[0].title;
9312 $scope.windowTitle = angular.element(window.document)[0].title;
9317 function $DocumentProvider() {
9318 this.$get = ['$window', function(window) {
9319 return jqLite(window.document);
9325 * @name $exceptionHandler
9329 * Any uncaught exception in angular expressions is delegated to this service.
9330 * The default implementation simply delegates to `$log.error` which logs it into
9331 * the browser console.
9333 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9334 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9339 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9340 * return function(exception, cause) {
9341 * exception.message += ' (caused by "' + cause + '")';
9347 * This example will override the normal action of `$exceptionHandler`, to make angular
9348 * exceptions fail hard when they happen, instead of just logging to the console.
9351 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9352 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9353 * (unless executed during a digest).
9355 * If you wish, you can manually delegate exceptions, e.g.
9356 * `try { ... } catch(e) { $exceptionHandler(e); }`
9358 * @param {Error} exception Exception associated with the error.
9359 * @param {string=} cause optional information about the context in which
9360 * the error was thrown.
9363 function $ExceptionHandlerProvider() {
9364 this.$get = ['$log', function($log) {
9365 return function(exception, cause) {
9366 $log.error.apply($log, arguments);
9371 var $$ForceReflowProvider = function() {
9372 this.$get = ['$document', function($document) {
9373 return function(domNode) {
9374 //the line below will force the browser to perform a repaint so
9375 //that all the animated elements within the animation frame will
9376 //be properly updated and drawn on screen. This is required to
9377 //ensure that the preparation animation is properly flushed so that
9378 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9379 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9380 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9381 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9383 if (!domNode.nodeType && domNode instanceof jqLite) {
9384 domNode = domNode[0];
9387 domNode = $document[0].body;
9389 return domNode.offsetWidth + 1;
9394 var APPLICATION_JSON = 'application/json';
9395 var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9396 var JSON_START = /^\[|^\{(?!\{)/;
9401 var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9402 var $httpMinErr = minErr('$http');
9403 var $httpMinErrLegacyFn = function(method) {
9405 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9409 function serializeValue(v) {
9411 return isDate(v) ? v.toISOString() : toJson(v);
9417 function $HttpParamSerializerProvider() {
9420 * @name $httpParamSerializer
9423 * Default {@link $http `$http`} params serializer that converts objects to strings
9424 * according to the following rules:
9426 * * `{'foo': 'bar'}` results in `foo=bar`
9427 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9428 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9429 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9431 * Note that serializer will sort the request parameters alphabetically.
9434 this.$get = function() {
9435 return function ngParamSerializer(params) {
9436 if (!params) return '';
9438 forEachSorted(params, function(value, key) {
9439 if (value === null || isUndefined(value)) return;
9440 if (isArray(value)) {
9441 forEach(value, function(v, k) {
9442 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9445 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9449 return parts.join('&');
9454 function $HttpParamSerializerJQLikeProvider() {
9457 * @name $httpParamSerializerJQLike
9460 * Alternative {@link $http `$http`} params serializer that follows
9461 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9462 * The serializer will also sort the params alphabetically.
9464 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9471 * paramSerializer: '$httpParamSerializerJQLike'
9475 * It is also possible to set it as the default `paramSerializer` in the
9476 * {@link $httpProvider#defaults `$httpProvider`}.
9478 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9479 * form data for submission:
9482 * .controller(function($http, $httpParamSerializerJQLike) {
9488 * data: $httpParamSerializerJQLike(myData),
9490 * 'Content-Type': 'application/x-www-form-urlencoded'
9498 this.$get = function() {
9499 return function jQueryLikeParamSerializer(params) {
9500 if (!params) return '';
9502 serialize(params, '', true);
9503 return parts.join('&');
9505 function serialize(toSerialize, prefix, topLevel) {
9506 if (toSerialize === null || isUndefined(toSerialize)) return;
9507 if (isArray(toSerialize)) {
9508 forEach(toSerialize, function(value, index) {
9509 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9511 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9512 forEachSorted(toSerialize, function(value, key) {
9513 serialize(value, prefix +
9514 (topLevel ? '' : '[') +
9516 (topLevel ? '' : ']'));
9519 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9526 function defaultHttpResponseTransform(data, headers) {
9527 if (isString(data)) {
9528 // Strip json vulnerability protection prefix and trim whitespace
9529 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9532 var contentType = headers('Content-Type');
9533 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9534 data = fromJson(tempData);
9542 function isJsonLike(str) {
9543 var jsonStart = str.match(JSON_START);
9544 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9548 * Parse headers into key value object
9550 * @param {string} headers Raw headers as a string
9551 * @returns {Object} Parsed headers as key value object
9553 function parseHeaders(headers) {
9554 var parsed = createMap(), i;
9556 function fillInParsed(key, val) {
9558 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9562 if (isString(headers)) {
9563 forEach(headers.split('\n'), function(line) {
9564 i = line.indexOf(':');
9565 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9567 } else if (isObject(headers)) {
9568 forEach(headers, function(headerVal, headerKey) {
9569 fillInParsed(lowercase(headerKey), trim(headerVal));
9578 * Returns a function that provides access to parsed headers.
9580 * Headers are lazy parsed when first requested.
9583 * @param {(string|Object)} headers Headers to provide access to.
9584 * @returns {function(string=)} Returns a getter function which if called with:
9586 * - if called with single an argument returns a single header value or null
9587 * - if called with no arguments returns an object containing all headers.
9589 function headersGetter(headers) {
9592 return function(name) {
9593 if (!headersObj) headersObj = parseHeaders(headers);
9596 var value = headersObj[lowercase(name)];
9597 if (value === void 0) {
9609 * Chain all given functions
9611 * This function is used for both request and response transforming
9613 * @param {*} data Data to transform.
9614 * @param {function(string=)} headers HTTP headers getter fn.
9615 * @param {number} status HTTP status code of the response.
9616 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9617 * @returns {*} Transformed data.
9619 function transformData(data, headers, status, fns) {
9620 if (isFunction(fns)) {
9621 return fns(data, headers, status);
9624 forEach(fns, function(fn) {
9625 data = fn(data, headers, status);
9632 function isSuccess(status) {
9633 return 200 <= status && status < 300;
9639 * @name $httpProvider
9641 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9643 function $HttpProvider() {
9646 * @name $httpProvider#defaults
9649 * Object containing default values for all {@link ng.$http $http} requests.
9651 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9652 * that will provide the cache for all requests who set their `cache` property to `true`.
9653 * If you set the `defaults.cache = false` then only requests that specify their own custom
9654 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9656 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9657 * Defaults value is `'XSRF-TOKEN'`.
9659 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9660 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9662 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9663 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9664 * setting default headers.
9665 * - **`defaults.headers.common`**
9666 * - **`defaults.headers.post`**
9667 * - **`defaults.headers.put`**
9668 * - **`defaults.headers.patch`**
9671 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9672 * used to the prepare string representation of request parameters (specified as an object).
9673 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9674 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9677 var defaults = this.defaults = {
9678 // transform incoming response data
9679 transformResponse: [defaultHttpResponseTransform],
9681 // transform outgoing request data
9682 transformRequest: [function(d) {
9683 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9689 'Accept': 'application/json, text/plain, */*'
9691 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9692 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9693 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9696 xsrfCookieName: 'XSRF-TOKEN',
9697 xsrfHeaderName: 'X-XSRF-TOKEN',
9699 paramSerializer: '$httpParamSerializer'
9702 var useApplyAsync = false;
9705 * @name $httpProvider#useApplyAsync
9708 * Configure $http service to combine processing of multiple http responses received at around
9709 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9710 * significant performance improvement for bigger applications that make many HTTP requests
9711 * concurrently (common during application bootstrap).
9713 * Defaults to false. If no value is specified, returns the current configured value.
9715 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9716 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9717 * to load and share the same digest cycle.
9719 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9720 * otherwise, returns the current configured value.
9722 this.useApplyAsync = function(value) {
9723 if (isDefined(value)) {
9724 useApplyAsync = !!value;
9727 return useApplyAsync;
9730 var useLegacyPromise = true;
9733 * @name $httpProvider#useLegacyPromiseExtensions
9736 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9737 * This should be used to make sure that applications work without these methods.
9739 * Defaults to true. If no value is specified, returns the current configured value.
9741 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9743 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9744 * otherwise, returns the current configured value.
9746 this.useLegacyPromiseExtensions = function(value) {
9747 if (isDefined(value)) {
9748 useLegacyPromise = !!value;
9751 return useLegacyPromise;
9756 * @name $httpProvider#interceptors
9759 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9760 * pre-processing of request or postprocessing of responses.
9762 * These service factories are ordered by request, i.e. they are applied in the same order as the
9763 * array, on request, but reverse order, on response.
9765 * {@link ng.$http#interceptors Interceptors detailed info}
9767 var interceptorFactories = this.interceptors = [];
9769 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9770 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9772 var defaultCache = $cacheFactory('$http');
9775 * Make sure that default param serializer is exposed as a function
9777 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9778 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9781 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9782 * The reversal is needed so that we can build up the interception chain around the
9785 var reversedInterceptors = [];
9787 forEach(interceptorFactories, function(interceptorFactory) {
9788 reversedInterceptors.unshift(isString(interceptorFactory)
9789 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9796 * @requires ng.$httpBackend
9797 * @requires $cacheFactory
9798 * @requires $rootScope
9800 * @requires $injector
9803 * The `$http` service is a core Angular service that facilitates communication with the remote
9804 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9805 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9807 * For unit testing applications that use `$http` service, see
9808 * {@link ngMock.$httpBackend $httpBackend mock}.
9810 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9811 * $resource} service.
9813 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9814 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9815 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9819 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9820 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9823 * // Simple GET request example:
9827 * }).then(function successCallback(response) {
9828 * // this callback will be called asynchronously
9829 * // when the response is available
9830 * }, function errorCallback(response) {
9831 * // called asynchronously if an error occurs
9832 * // or server returns response with an error status.
9836 * The response object has these properties:
9838 * - **data** – `{string|Object}` – The response body transformed with the transform
9840 * - **status** – `{number}` – HTTP status code of the response.
9841 * - **headers** – `{function([headerName])}` – Header getter function.
9842 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9843 * - **statusText** – `{string}` – HTTP status text of the response.
9845 * A response status code between 200 and 299 is considered a success status and
9846 * will result in the success callback being called. Note that if the response is a redirect,
9847 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9848 * called for such responses.
9851 * ## Shortcut methods
9853 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9854 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9858 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9859 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9862 * Complete list of shortcut methods:
9864 * - {@link ng.$http#get $http.get}
9865 * - {@link ng.$http#head $http.head}
9866 * - {@link ng.$http#post $http.post}
9867 * - {@link ng.$http#put $http.put}
9868 * - {@link ng.$http#delete $http.delete}
9869 * - {@link ng.$http#jsonp $http.jsonp}
9870 * - {@link ng.$http#patch $http.patch}
9873 * ## Writing Unit Tests that use $http
9874 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9875 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9876 * request using trained responses.
9879 * $httpBackend.expectGET(...);
9881 * $httpBackend.flush();
9884 * ## Deprecation Notice
9885 * <div class="alert alert-danger">
9886 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9887 * Use the standard `then` method instead.
9888 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9889 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9892 * ## Setting HTTP Headers
9894 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9895 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9896 * object, which currently contains this default configuration:
9898 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9899 * - `Accept: application/json, text/plain, * / *`
9900 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9901 * - `Content-Type: application/json`
9902 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9903 * - `Content-Type: application/json`
9905 * To add or overwrite these defaults, simply add or remove a property from these configuration
9906 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9907 * with the lowercased HTTP method name as the key, e.g.
9908 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9910 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9911 * fashion. For example:
9914 * module.run(function($http) {
9915 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9919 * In addition, you can supply a `headers` property in the config object passed when
9920 * calling `$http(config)`, which overrides the defaults without changing them globally.
9922 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9923 * Use the `headers` property, setting the desired header to `undefined`. For example:
9928 * url: 'http://example.com',
9930 * 'Content-Type': undefined
9932 * data: { test: 'test' }
9935 * $http(req).then(function(){...}, function(){...});
9938 * ## Transforming Requests and Responses
9940 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9941 * and `transformResponse`. These properties can be a single function that returns
9942 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9943 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9945 * ### Default Transformations
9947 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9948 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9949 * then these will be applied.
9951 * You can augment or replace the default transformations by modifying these properties by adding to or
9952 * replacing the array.
9954 * Angular provides the following default transformations:
9956 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9958 * - If the `data` property of the request configuration object contains an object, serialize it
9961 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9963 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9964 * - If JSON response is detected, deserialize it using a JSON parser.
9967 * ### Overriding the Default Transformations Per Request
9969 * If you wish override the request/response transformations only for a single request then provide
9970 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9973 * Note that if you provide these properties on the config object the default transformations will be
9974 * overwritten. If you wish to augment the default transformations then you must include them in your
9975 * local transformation array.
9977 * The following code demonstrates adding a new response transformation to be run after the default response
9978 * transformations have been run.
9981 * function appendTransform(defaults, transform) {
9983 * // We can't guarantee that the default transformation is an array
9984 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9986 * // Append the new transformation to the defaults
9987 * return defaults.concat(transform);
9993 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9994 * return doTransform(value);
10002 * To enable caching, set the request configuration `cache` property to `true` (to use default
10003 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
10004 * When the cache is enabled, `$http` stores the response from the server in the specified
10005 * cache. The next time the same request is made, the response is served from the cache without
10006 * sending a request to the server.
10008 * Note that even if the response is served from cache, delivery of the data is asynchronous in
10009 * the same way that real requests are.
10011 * If there are multiple GET requests for the same URL that should be cached using the same
10012 * cache, but the cache is not populated yet, only one request to the server will be made and
10013 * the remaining requests will be fulfilled using the response from the first request.
10015 * You can change the default cache to a new object (built with
10016 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
10017 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
10018 * their `cache` property to `true` will now use this cache object.
10020 * If you set the default cache to `false` then only requests that specify their own custom
10021 * cache object will be cached.
10025 * Before you start creating interceptors, be sure to understand the
10026 * {@link ng.$q $q and deferred/promise APIs}.
10028 * For purposes of global error handling, authentication, or any kind of synchronous or
10029 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
10030 * able to intercept requests before they are handed to the server and
10031 * responses before they are handed over to the application code that
10032 * initiated these requests. The interceptors leverage the {@link ng.$q
10033 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
10035 * The interceptors are service factories that are registered with the `$httpProvider` by
10036 * adding them to the `$httpProvider.interceptors` array. The factory is called and
10037 * injected with dependencies (if specified) and returns the interceptor.
10039 * There are two kinds of interceptors (and two kinds of rejection interceptors):
10041 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
10042 * modify the `config` object or create a new one. The function needs to return the `config`
10043 * object directly, or a promise containing the `config` or a new `config` object.
10044 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
10045 * resolved with a rejection.
10046 * * `response`: interceptors get called with http `response` object. The function is free to
10047 * modify the `response` object or create a new one. The function needs to return the `response`
10048 * object directly, or as a promise containing the `response` or a new `response` object.
10049 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
10050 * resolved with a rejection.
10054 * // register the interceptor as a service
10055 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
10057 * // optional method
10058 * 'request': function(config) {
10059 * // do something on success
10063 * // optional method
10064 * 'requestError': function(rejection) {
10065 * // do something on error
10066 * if (canRecover(rejection)) {
10067 * return responseOrNewPromise
10069 * return $q.reject(rejection);
10074 * // optional method
10075 * 'response': function(response) {
10076 * // do something on success
10080 * // optional method
10081 * 'responseError': function(rejection) {
10082 * // do something on error
10083 * if (canRecover(rejection)) {
10084 * return responseOrNewPromise
10086 * return $q.reject(rejection);
10091 * $httpProvider.interceptors.push('myHttpInterceptor');
10094 * // alternatively, register the interceptor via an anonymous factory
10095 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10097 * 'request': function(config) {
10101 * 'response': function(response) {
10108 * ## Security Considerations
10110 * When designing web applications, consider security threats from:
10112 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10113 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10115 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10116 * pre-configured with strategies that address these issues, but for this to work backend server
10117 * cooperation is required.
10119 * ### JSON Vulnerability Protection
10121 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10122 * allows third party website to turn your JSON resource URL into
10123 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10124 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10125 * Angular will automatically strip the prefix before processing it as JSON.
10127 * For example if your server needs to return:
10132 * which is vulnerable to attack, your server can return:
10138 * Angular will strip the prefix, before processing the JSON.
10141 * ### Cross Site Request Forgery (XSRF) Protection
10143 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10144 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10145 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10146 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10147 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10148 * the XHR came from JavaScript running on your domain. The header will not be set for
10149 * cross-domain requests.
10151 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10152 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10153 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10154 * that only JavaScript running on your domain could have sent the request. The token must be
10155 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10156 * making up its own tokens). We recommend that the token is a digest of your site's
10157 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography))
10158 * for added security.
10160 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10161 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10162 * or the per-request config object.
10164 * In order to prevent collisions in environments where multiple Angular apps share the
10165 * same domain or subdomain, we recommend that each application uses unique cookie name.
10167 * @param {object} config Object describing the request to be made and how it should be
10168 * processed. The object has following properties:
10170 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10171 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10172 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10173 * with the `paramSerializer` and appended as GET parameters.
10174 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10175 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10176 * HTTP headers to send to the server. If the return value of a function is null, the
10177 * header will not be sent. Functions accept a config object as an argument.
10178 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10179 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10180 * - **transformRequest** –
10181 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10182 * transform function or an array of such functions. The transform function takes the http
10183 * request body and headers and returns its transformed (typically serialized) version.
10184 * See {@link ng.$http#overriding-the-default-transformations-per-request
10185 * Overriding the Default Transformations}
10186 * - **transformResponse** –
10187 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10188 * transform function or an array of such functions. The transform function takes the http
10189 * response body, headers and status and returns its transformed (typically deserialized) version.
10190 * See {@link ng.$http#overriding-the-default-transformations-per-request
10191 * Overriding the Default TransformationjqLiks}
10192 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10193 * prepare the string representation of request parameters (specified as an object).
10194 * If specified as string, it is interpreted as function registered with the
10195 * {@link $injector $injector}, which means you can create your own serializer
10196 * by registering it as a {@link auto.$provide#service service}.
10197 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10198 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10199 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10200 * GET request, otherwise if a cache instance built with
10201 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10203 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10204 * that should abort the request when resolved.
10205 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10206 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10207 * for more information.
10208 * - **responseType** - `{string}` - see
10209 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10211 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10212 * when the request succeeds or fails.
10215 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10216 * requests. This is primarily meant to be used for debugging purposes.
10220 <example module="httpExample">
10221 <file name="index.html">
10222 <div ng-controller="FetchController">
10223 <select ng-model="method" aria-label="Request method">
10224 <option>GET</option>
10225 <option>JSONP</option>
10227 <input type="text" ng-model="url" size="80" aria-label="URL" />
10228 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10229 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10230 <button id="samplejsonpbtn"
10231 ng-click="updateModel('JSONP',
10232 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10235 <button id="invalidjsonpbtn"
10236 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10239 <pre>http status code: {{status}}</pre>
10240 <pre>http response data: {{data}}</pre>
10243 <file name="script.js">
10244 angular.module('httpExample', [])
10245 .controller('FetchController', ['$scope', '$http', '$templateCache',
10246 function($scope, $http, $templateCache) {
10247 $scope.method = 'GET';
10248 $scope.url = 'http-hello.html';
10250 $scope.fetch = function() {
10251 $scope.code = null;
10252 $scope.response = null;
10254 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10255 then(function(response) {
10256 $scope.status = response.status;
10257 $scope.data = response.data;
10258 }, function(response) {
10259 $scope.data = response.data || "Request failed";
10260 $scope.status = response.status;
10264 $scope.updateModel = function(method, url) {
10265 $scope.method = method;
10270 <file name="http-hello.html">
10273 <file name="protractor.js" type="protractor">
10274 var status = element(by.binding('status'));
10275 var data = element(by.binding('data'));
10276 var fetchBtn = element(by.id('fetchbtn'));
10277 var sampleGetBtn = element(by.id('samplegetbtn'));
10278 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10279 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10281 it('should make an xhr GET request', function() {
10282 sampleGetBtn.click();
10284 expect(status.getText()).toMatch('200');
10285 expect(data.getText()).toMatch(/Hello, \$http!/);
10288 // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10289 // it('should make a JSONP request to angularjs.org', function() {
10290 // sampleJsonpBtn.click();
10291 // fetchBtn.click();
10292 // expect(status.getText()).toMatch('200');
10293 // expect(data.getText()).toMatch(/Super Hero!/);
10296 it('should make JSONP request to invalid URL and invoke the error handler',
10298 invalidJsonpBtn.click();
10300 expect(status.getText()).toMatch('0');
10301 expect(data.getText()).toMatch('Request failed');
10306 function $http(requestConfig) {
10308 if (!angular.isObject(requestConfig)) {
10309 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10312 var config = extend({
10314 transformRequest: defaults.transformRequest,
10315 transformResponse: defaults.transformResponse,
10316 paramSerializer: defaults.paramSerializer
10319 config.headers = mergeHeaders(requestConfig);
10320 config.method = uppercase(config.method);
10321 config.paramSerializer = isString(config.paramSerializer) ?
10322 $injector.get(config.paramSerializer) : config.paramSerializer;
10324 var serverRequest = function(config) {
10325 var headers = config.headers;
10326 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10328 // strip content-type if data is undefined
10329 if (isUndefined(reqData)) {
10330 forEach(headers, function(value, header) {
10331 if (lowercase(header) === 'content-type') {
10332 delete headers[header];
10337 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10338 config.withCredentials = defaults.withCredentials;
10342 return sendReq(config, reqData).then(transformResponse, transformResponse);
10345 var chain = [serverRequest, undefined];
10346 var promise = $q.when(config);
10348 // apply interceptors
10349 forEach(reversedInterceptors, function(interceptor) {
10350 if (interceptor.request || interceptor.requestError) {
10351 chain.unshift(interceptor.request, interceptor.requestError);
10353 if (interceptor.response || interceptor.responseError) {
10354 chain.push(interceptor.response, interceptor.responseError);
10358 while (chain.length) {
10359 var thenFn = chain.shift();
10360 var rejectFn = chain.shift();
10362 promise = promise.then(thenFn, rejectFn);
10365 if (useLegacyPromise) {
10366 promise.success = function(fn) {
10367 assertArgFn(fn, 'fn');
10369 promise.then(function(response) {
10370 fn(response.data, response.status, response.headers, config);
10375 promise.error = function(fn) {
10376 assertArgFn(fn, 'fn');
10378 promise.then(null, function(response) {
10379 fn(response.data, response.status, response.headers, config);
10384 promise.success = $httpMinErrLegacyFn('success');
10385 promise.error = $httpMinErrLegacyFn('error');
10390 function transformResponse(response) {
10391 // make a copy since the response must be cacheable
10392 var resp = extend({}, response);
10393 resp.data = transformData(response.data, response.headers, response.status,
10394 config.transformResponse);
10395 return (isSuccess(response.status))
10400 function executeHeaderFns(headers, config) {
10401 var headerContent, processedHeaders = {};
10403 forEach(headers, function(headerFn, header) {
10404 if (isFunction(headerFn)) {
10405 headerContent = headerFn(config);
10406 if (headerContent != null) {
10407 processedHeaders[header] = headerContent;
10410 processedHeaders[header] = headerFn;
10414 return processedHeaders;
10417 function mergeHeaders(config) {
10418 var defHeaders = defaults.headers,
10419 reqHeaders = extend({}, config.headers),
10420 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10422 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10424 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10425 defaultHeadersIteration:
10426 for (defHeaderName in defHeaders) {
10427 lowercaseDefHeaderName = lowercase(defHeaderName);
10429 for (reqHeaderName in reqHeaders) {
10430 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10431 continue defaultHeadersIteration;
10435 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10438 // execute if header value is a function for merged headers
10439 return executeHeaderFns(reqHeaders, shallowCopy(config));
10443 $http.pendingRequests = [];
10450 * Shortcut method to perform `GET` request.
10452 * @param {string} url Relative or absolute URL specifying the destination of the request
10453 * @param {Object=} config Optional configuration object
10454 * @returns {HttpPromise} Future object
10459 * @name $http#delete
10462 * Shortcut method to perform `DELETE` request.
10464 * @param {string} url Relative or absolute URL specifying the destination of the request
10465 * @param {Object=} config Optional configuration object
10466 * @returns {HttpPromise} Future object
10474 * Shortcut method to perform `HEAD` request.
10476 * @param {string} url Relative or absolute URL specifying the destination of the request
10477 * @param {Object=} config Optional configuration object
10478 * @returns {HttpPromise} Future object
10483 * @name $http#jsonp
10486 * Shortcut method to perform `JSONP` request.
10488 * @param {string} url Relative or absolute URL specifying the destination of the request.
10489 * The name of the callback should be the string `JSON_CALLBACK`.
10490 * @param {Object=} config Optional configuration object
10491 * @returns {HttpPromise} Future object
10493 createShortMethods('get', 'delete', 'head', 'jsonp');
10500 * Shortcut method to perform `POST` request.
10502 * @param {string} url Relative or absolute URL specifying the destination of the request
10503 * @param {*} data Request content
10504 * @param {Object=} config Optional configuration object
10505 * @returns {HttpPromise} Future object
10513 * Shortcut method to perform `PUT` request.
10515 * @param {string} url Relative or absolute URL specifying the destination of the request
10516 * @param {*} data Request content
10517 * @param {Object=} config Optional configuration object
10518 * @returns {HttpPromise} Future object
10523 * @name $http#patch
10526 * Shortcut method to perform `PATCH` request.
10528 * @param {string} url Relative or absolute URL specifying the destination of the request
10529 * @param {*} data Request content
10530 * @param {Object=} config Optional configuration object
10531 * @returns {HttpPromise} Future object
10533 createShortMethodsWithData('post', 'put', 'patch');
10537 * @name $http#defaults
10540 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10541 * default headers, withCredentials as well as request and response transformations.
10543 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10545 $http.defaults = defaults;
10551 function createShortMethods(names) {
10552 forEach(arguments, function(name) {
10553 $http[name] = function(url, config) {
10554 return $http(extend({}, config || {}, {
10563 function createShortMethodsWithData(name) {
10564 forEach(arguments, function(name) {
10565 $http[name] = function(url, data, config) {
10566 return $http(extend({}, config || {}, {
10577 * Makes the request.
10579 * !!! ACCESSES CLOSURE VARS:
10580 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10582 function sendReq(config, reqData) {
10583 var deferred = $q.defer(),
10584 promise = deferred.promise,
10587 reqHeaders = config.headers,
10588 url = buildUrl(config.url, config.paramSerializer(config.params));
10590 $http.pendingRequests.push(config);
10591 promise.then(removePendingReq, removePendingReq);
10594 if ((config.cache || defaults.cache) && config.cache !== false &&
10595 (config.method === 'GET' || config.method === 'JSONP')) {
10596 cache = isObject(config.cache) ? config.cache
10597 : isObject(defaults.cache) ? defaults.cache
10602 cachedResp = cache.get(url);
10603 if (isDefined(cachedResp)) {
10604 if (isPromiseLike(cachedResp)) {
10605 // cached request has already been sent, but there is no response yet
10606 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10608 // serving from cache
10609 if (isArray(cachedResp)) {
10610 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10612 resolvePromise(cachedResp, 200, {}, 'OK');
10616 // put the promise for the non-transformed response into cache as a placeholder
10617 cache.put(url, promise);
10622 // if we won't have the response in cache, set the xsrf headers and
10623 // send the request to the backend
10624 if (isUndefined(cachedResp)) {
10625 var xsrfValue = urlIsSameOrigin(config.url)
10626 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10629 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10632 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10633 config.withCredentials, config.responseType);
10640 * Callback registered to $httpBackend():
10641 * - caches the response if desired
10642 * - resolves the raw $http promise
10645 function done(status, response, headersString, statusText) {
10647 if (isSuccess(status)) {
10648 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10650 // remove promise from the cache
10655 function resolveHttpPromise() {
10656 resolvePromise(response, status, headersString, statusText);
10659 if (useApplyAsync) {
10660 $rootScope.$applyAsync(resolveHttpPromise);
10662 resolveHttpPromise();
10663 if (!$rootScope.$$phase) $rootScope.$apply();
10669 * Resolves the raw $http promise.
10671 function resolvePromise(response, status, headers, statusText) {
10672 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10673 status = status >= -1 ? status : 0;
10675 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10678 headers: headersGetter(headers),
10680 statusText: statusText
10684 function resolvePromiseWithResult(result) {
10685 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10688 function removePendingReq() {
10689 var idx = $http.pendingRequests.indexOf(config);
10690 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10695 function buildUrl(url, serializedParams) {
10696 if (serializedParams.length > 0) {
10697 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10706 * @name $xhrFactory
10709 * Factory function used to create XMLHttpRequest objects.
10711 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10714 * angular.module('myApp', [])
10715 * .factory('$xhrFactory', function() {
10716 * return function createXhr(method, url) {
10717 * return new window.XMLHttpRequest({mozSystem: true});
10722 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10723 * @param {string} url URL of the request.
10725 function $xhrFactoryProvider() {
10726 this.$get = function() {
10727 return function createXhr() {
10728 return new window.XMLHttpRequest();
10735 * @name $httpBackend
10736 * @requires $window
10737 * @requires $document
10738 * @requires $xhrFactory
10741 * HTTP backend used by the {@link ng.$http service} that delegates to
10742 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10744 * You should never need to use this service directly, instead use the higher-level abstractions:
10745 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10747 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10748 * $httpBackend} which can be trained with responses.
10750 function $HttpBackendProvider() {
10751 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10752 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10756 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10757 // TODO(vojta): fix the signature
10758 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10759 $browser.$$incOutstandingRequestCount();
10760 url = url || $browser.url();
10762 if (lowercase(method) == 'jsonp') {
10763 var callbackId = '_' + (callbacks.counter++).toString(36);
10764 callbacks[callbackId] = function(data) {
10765 callbacks[callbackId].data = data;
10766 callbacks[callbackId].called = true;
10769 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10770 callbackId, function(status, text) {
10771 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10772 callbacks[callbackId] = noop;
10776 var xhr = createXhr(method, url);
10778 xhr.open(method, url, true);
10779 forEach(headers, function(value, key) {
10780 if (isDefined(value)) {
10781 xhr.setRequestHeader(key, value);
10785 xhr.onload = function requestLoaded() {
10786 var statusText = xhr.statusText || '';
10788 // responseText is the old-school way of retrieving response (supported by IE9)
10789 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10790 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10792 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10793 var status = xhr.status === 1223 ? 204 : xhr.status;
10795 // fix status code when it is 0 (0 status is undocumented).
10796 // Occurs when accessing file resources or on Android 4.1 stock browser
10797 // while retrieving files from application cache.
10798 if (status === 0) {
10799 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10802 completeRequest(callback,
10805 xhr.getAllResponseHeaders(),
10809 var requestError = function() {
10810 // The response is always empty
10811 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10812 completeRequest(callback, -1, null, null, '');
10815 xhr.onerror = requestError;
10816 xhr.onabort = requestError;
10818 if (withCredentials) {
10819 xhr.withCredentials = true;
10822 if (responseType) {
10824 xhr.responseType = responseType;
10826 // WebKit added support for the json responseType value on 09/03/2013
10827 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10828 // known to throw when setting the value "json" as the response type. Other older
10829 // browsers implementing the responseType
10831 // The json response type can be ignored if not supported, because JSON payloads are
10832 // parsed on the client-side regardless.
10833 if (responseType !== 'json') {
10839 xhr.send(isUndefined(post) ? null : post);
10843 var timeoutId = $browserDefer(timeoutRequest, timeout);
10844 } else if (isPromiseLike(timeout)) {
10845 timeout.then(timeoutRequest);
10849 function timeoutRequest() {
10850 jsonpDone && jsonpDone();
10851 xhr && xhr.abort();
10854 function completeRequest(callback, status, response, headersString, statusText) {
10855 // cancel timeout and subsequent timeout promise resolution
10856 if (isDefined(timeoutId)) {
10857 $browserDefer.cancel(timeoutId);
10859 jsonpDone = xhr = null;
10861 callback(status, response, headersString, statusText);
10862 $browser.$$completeOutstandingRequest(noop);
10866 function jsonpReq(url, callbackId, done) {
10867 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10868 // - fetches local scripts via XHR and evals them
10869 // - adds and immediately removes script elements from the document
10870 var script = rawDocument.createElement('script'), callback = null;
10871 script.type = "text/javascript";
10873 script.async = true;
10875 callback = function(event) {
10876 removeEventListenerFn(script, "load", callback);
10877 removeEventListenerFn(script, "error", callback);
10878 rawDocument.body.removeChild(script);
10881 var text = "unknown";
10884 if (event.type === "load" && !callbacks[callbackId].called) {
10885 event = { type: "error" };
10888 status = event.type === "error" ? 404 : 200;
10892 done(status, text);
10896 addEventListenerFn(script, "load", callback);
10897 addEventListenerFn(script, "error", callback);
10898 rawDocument.body.appendChild(script);
10903 var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10904 $interpolateMinErr.throwNoconcat = function(text) {
10905 throw $interpolateMinErr('noconcat',
10906 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10907 "interpolations that concatenate multiple expressions when a trusted value is " +
10908 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10911 $interpolateMinErr.interr = function(text, err) {
10912 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10917 * @name $interpolateProvider
10921 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10924 <example module="customInterpolationApp">
10925 <file name="index.html">
10927 var customInterpolationApp = angular.module('customInterpolationApp', []);
10929 customInterpolationApp.config(function($interpolateProvider) {
10930 $interpolateProvider.startSymbol('//');
10931 $interpolateProvider.endSymbol('//');
10935 customInterpolationApp.controller('DemoController', function() {
10936 this.label = "This binding is brought you by // interpolation symbols.";
10939 <div ng-app="App" ng-controller="DemoController as demo">
10943 <file name="protractor.js" type="protractor">
10944 it('should interpolate binding with custom symbols', function() {
10945 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10950 function $InterpolateProvider() {
10951 var startSymbol = '{{';
10952 var endSymbol = '}}';
10956 * @name $interpolateProvider#startSymbol
10958 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10960 * @param {string=} value new value to set the starting symbol to.
10961 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10963 this.startSymbol = function(value) {
10965 startSymbol = value;
10968 return startSymbol;
10974 * @name $interpolateProvider#endSymbol
10976 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10978 * @param {string=} value new value to set the ending symbol to.
10979 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10981 this.endSymbol = function(value) {
10991 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10992 var startSymbolLength = startSymbol.length,
10993 endSymbolLength = endSymbol.length,
10994 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
10995 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
10997 function escape(ch) {
10998 return '\\\\\\' + ch;
11001 function unescapeText(text) {
11002 return text.replace(escapedStartRegexp, startSymbol).
11003 replace(escapedEndRegexp, endSymbol);
11006 function stringify(value) {
11007 if (value == null) { // null || undefined
11010 switch (typeof value) {
11014 value = '' + value;
11017 value = toJson(value);
11025 * @name $interpolate
11033 * Compiles a string with markup into an interpolation function. This service is used by the
11034 * HTML {@link ng.$compile $compile} service for data binding. See
11035 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
11036 * interpolation markup.
11040 * var $interpolate = ...; // injected
11041 * var exp = $interpolate('Hello {{name | uppercase}}!');
11042 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
11045 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
11046 * `true`, the interpolation function will return `undefined` unless all embedded expressions
11047 * evaluate to a value other than `undefined`.
11050 * var $interpolate = ...; // injected
11051 * var context = {greeting: 'Hello', name: undefined };
11053 * // default "forgiving" mode
11054 * var exp = $interpolate('{{greeting}} {{name}}!');
11055 * expect(exp(context)).toEqual('Hello !');
11057 * // "allOrNothing" mode
11058 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
11059 * expect(exp(context)).toBeUndefined();
11060 * context.name = 'Angular';
11061 * expect(exp(context)).toEqual('Hello Angular!');
11064 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
11066 * ####Escaped Interpolation
11067 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
11068 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
11069 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
11072 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
11073 * degree, while also enabling code examples to work without relying on the
11074 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11076 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11077 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
11078 * interpolation start/end markers with their escaped counterparts.**
11080 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11081 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11082 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11083 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11084 * this is typically useful only when user-data is used in rendering a template from the server, or
11085 * when otherwise untrusted data is used by a directive.
11088 * <file name="index.html">
11089 * <div ng-init="username='A user'">
11090 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11092 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11093 * application, but fails to accomplish their task, because the server has correctly
11094 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11096 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11097 * from the database by an administrator.</p>
11102 * @param {string} text The text with markup to interpolate.
11103 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11104 * embedded expression in order to return an interpolation function. Strings with no
11105 * embedded expression will return null for the interpolation function.
11106 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11107 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11108 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11109 * provides Strict Contextual Escaping for details.
11110 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11111 * unless all embedded expressions evaluate to a value other than `undefined`.
11112 * @returns {function(context)} an interpolation function which is used to compute the
11113 * interpolated string. The function has these parameters:
11115 * - `context`: evaluation context for all expressions embedded in the interpolated text
11117 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11118 allOrNothing = !!allOrNothing;
11124 textLength = text.length,
11127 expressionPositions = [];
11129 while (index < textLength) {
11130 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11131 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11132 if (index !== startIndex) {
11133 concat.push(unescapeText(text.substring(index, startIndex)));
11135 exp = text.substring(startIndex + startSymbolLength, endIndex);
11136 expressions.push(exp);
11137 parseFns.push($parse(exp, parseStringifyInterceptor));
11138 index = endIndex + endSymbolLength;
11139 expressionPositions.push(concat.length);
11142 // we did not find an interpolation, so we have to add the remainder to the separators array
11143 if (index !== textLength) {
11144 concat.push(unescapeText(text.substring(index)));
11150 // Concatenating expressions makes it hard to reason about whether some combination of
11151 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11152 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11153 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11154 // make it obvious that you bound the value to some user controlled value. This helps reduce
11155 // the load when auditing for XSS issues.
11156 if (trustedContext && concat.length > 1) {
11157 $interpolateMinErr.throwNoconcat(text);
11160 if (!mustHaveExpression || expressions.length) {
11161 var compute = function(values) {
11162 for (var i = 0, ii = expressions.length; i < ii; i++) {
11163 if (allOrNothing && isUndefined(values[i])) return;
11164 concat[expressionPositions[i]] = values[i];
11166 return concat.join('');
11169 var getValue = function(value) {
11170 return trustedContext ?
11171 $sce.getTrusted(trustedContext, value) :
11172 $sce.valueOf(value);
11175 return extend(function interpolationFn(context) {
11177 var ii = expressions.length;
11178 var values = new Array(ii);
11181 for (; i < ii; i++) {
11182 values[i] = parseFns[i](context);
11185 return compute(values);
11187 $exceptionHandler($interpolateMinErr.interr(text, err));
11191 // all of these properties are undocumented for now
11192 exp: text, //just for compatibility with regular watchers created via $watch
11193 expressions: expressions,
11194 $$watchDelegate: function(scope, listener) {
11196 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11197 var currValue = compute(values);
11198 if (isFunction(listener)) {
11199 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11201 lastValue = currValue;
11207 function parseStringifyInterceptor(value) {
11209 value = getValue(value);
11210 return allOrNothing && !isDefined(value) ? value : stringify(value);
11212 $exceptionHandler($interpolateMinErr.interr(text, err));
11220 * @name $interpolate#startSymbol
11222 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11224 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11227 * @returns {string} start symbol.
11229 $interpolate.startSymbol = function() {
11230 return startSymbol;
11236 * @name $interpolate#endSymbol
11238 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11240 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11243 * @returns {string} end symbol.
11245 $interpolate.endSymbol = function() {
11249 return $interpolate;
11253 function $IntervalProvider() {
11254 this.$get = ['$rootScope', '$window', '$q', '$$q',
11255 function($rootScope, $window, $q, $$q) {
11256 var intervals = {};
11264 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11267 * The return value of registering an interval function is a promise. This promise will be
11268 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11269 * run indefinitely if `count` is not defined. The value of the notification will be the
11270 * number of iterations that have run.
11271 * To cancel an interval, call `$interval.cancel(promise)`.
11273 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11274 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11277 * <div class="alert alert-warning">
11278 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11279 * with them. In particular they are not automatically destroyed when a controller's scope or a
11280 * directive's element are destroyed.
11281 * You should take this into consideration and make sure to always cancel the interval at the
11282 * appropriate moment. See the example below for more details on how and when to do this.
11285 * @param {function()} fn A function that should be called repeatedly.
11286 * @param {number} delay Number of milliseconds between each function call.
11287 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11289 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11290 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11291 * @param {...*=} Pass additional parameters to the executed function.
11292 * @returns {promise} A promise which will be notified on each iteration.
11295 * <example module="intervalExample">
11296 * <file name="index.html">
11298 * angular.module('intervalExample', [])
11299 * .controller('ExampleController', ['$scope', '$interval',
11300 * function($scope, $interval) {
11301 * $scope.format = 'M/d/yy h:mm:ss a';
11302 * $scope.blood_1 = 100;
11303 * $scope.blood_2 = 120;
11306 * $scope.fight = function() {
11307 * // Don't start a new fight if we are already fighting
11308 * if ( angular.isDefined(stop) ) return;
11310 * stop = $interval(function() {
11311 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11312 * $scope.blood_1 = $scope.blood_1 - 3;
11313 * $scope.blood_2 = $scope.blood_2 - 4;
11315 * $scope.stopFight();
11320 * $scope.stopFight = function() {
11321 * if (angular.isDefined(stop)) {
11322 * $interval.cancel(stop);
11323 * stop = undefined;
11327 * $scope.resetFight = function() {
11328 * $scope.blood_1 = 100;
11329 * $scope.blood_2 = 120;
11332 * $scope.$on('$destroy', function() {
11333 * // Make sure that the interval is destroyed too
11334 * $scope.stopFight();
11337 * // Register the 'myCurrentTime' directive factory method.
11338 * // We inject $interval and dateFilter service since the factory method is DI.
11339 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11340 * function($interval, dateFilter) {
11341 * // return the directive link function. (compile function not needed)
11342 * return function(scope, element, attrs) {
11343 * var format, // date format
11344 * stopTime; // so that we can cancel the time updates
11346 * // used to update the UI
11347 * function updateTime() {
11348 * element.text(dateFilter(new Date(), format));
11351 * // watch the expression, and update the UI on change.
11352 * scope.$watch(attrs.myCurrentTime, function(value) {
11357 * stopTime = $interval(updateTime, 1000);
11359 * // listen on DOM destroy (removal) event, and cancel the next UI update
11360 * // to prevent updating time after the DOM element was removed.
11361 * element.on('$destroy', function() {
11362 * $interval.cancel(stopTime);
11369 * <div ng-controller="ExampleController">
11370 * <label>Date format: <input ng-model="format"></label> <hr/>
11371 * Current time is: <span my-current-time="format"></span>
11373 * Blood 1 : <font color='red'>{{blood_1}}</font>
11374 * Blood 2 : <font color='red'>{{blood_2}}</font>
11375 * <button type="button" data-ng-click="fight()">Fight</button>
11376 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11377 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11384 function interval(fn, delay, count, invokeApply) {
11385 var hasParams = arguments.length > 4,
11386 args = hasParams ? sliceArgs(arguments, 4) : [],
11387 setInterval = $window.setInterval,
11388 clearInterval = $window.clearInterval,
11390 skipApply = (isDefined(invokeApply) && !invokeApply),
11391 deferred = (skipApply ? $$q : $q).defer(),
11392 promise = deferred.promise;
11394 count = isDefined(count) ? count : 0;
11396 promise.then(null, null, (!hasParams) ? fn : function() {
11397 fn.apply(null, args);
11400 promise.$$intervalId = setInterval(function tick() {
11401 deferred.notify(iteration++);
11403 if (count > 0 && iteration >= count) {
11404 deferred.resolve(iteration);
11405 clearInterval(promise.$$intervalId);
11406 delete intervals[promise.$$intervalId];
11409 if (!skipApply) $rootScope.$apply();
11413 intervals[promise.$$intervalId] = deferred;
11421 * @name $interval#cancel
11424 * Cancels a task associated with the `promise`.
11426 * @param {Promise=} promise returned by the `$interval` function.
11427 * @returns {boolean} Returns `true` if the task was successfully canceled.
11429 interval.cancel = function(promise) {
11430 if (promise && promise.$$intervalId in intervals) {
11431 intervals[promise.$$intervalId].reject('canceled');
11432 $window.clearInterval(promise.$$intervalId);
11433 delete intervals[promise.$$intervalId];
11448 * $locale service provides localization rules for various Angular components. As of right now the
11449 * only public api is:
11451 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11454 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11455 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11456 var $locationMinErr = minErr('$location');
11460 * Encode path using encodeUriSegment, ignoring forward slashes
11462 * @param {string} path Path to encode
11463 * @returns {string}
11465 function encodePath(path) {
11466 var segments = path.split('/'),
11467 i = segments.length;
11470 segments[i] = encodeUriSegment(segments[i]);
11473 return segments.join('/');
11476 function parseAbsoluteUrl(absoluteUrl, locationObj) {
11477 var parsedUrl = urlResolve(absoluteUrl);
11479 locationObj.$$protocol = parsedUrl.protocol;
11480 locationObj.$$host = parsedUrl.hostname;
11481 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11485 function parseAppUrl(relativeUrl, locationObj) {
11486 var prefixed = (relativeUrl.charAt(0) !== '/');
11488 relativeUrl = '/' + relativeUrl;
11490 var match = urlResolve(relativeUrl);
11491 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11492 match.pathname.substring(1) : match.pathname);
11493 locationObj.$$search = parseKeyValue(match.search);
11494 locationObj.$$hash = decodeURIComponent(match.hash);
11496 // make sure path starts with '/';
11497 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11498 locationObj.$$path = '/' + locationObj.$$path;
11505 * @param {string} begin
11506 * @param {string} whole
11507 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11510 function beginsWith(begin, whole) {
11511 if (whole.indexOf(begin) === 0) {
11512 return whole.substr(begin.length);
11517 function stripHash(url) {
11518 var index = url.indexOf('#');
11519 return index == -1 ? url : url.substr(0, index);
11522 function trimEmptyHash(url) {
11523 return url.replace(/(#.+)|#$/, '$1');
11527 function stripFile(url) {
11528 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11531 /* return the server only (scheme://host:port) */
11532 function serverBase(url) {
11533 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11538 * LocationHtml5Url represents an url
11539 * This object is exposed as $location service when HTML5 mode is enabled and supported
11542 * @param {string} appBase application base URL
11543 * @param {string} appBaseNoFile application base URL stripped of any filename
11544 * @param {string} basePrefix url path prefix
11546 function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11547 this.$$html5 = true;
11548 basePrefix = basePrefix || '';
11549 parseAbsoluteUrl(appBase, this);
11553 * Parse given html5 (regular) url string into properties
11554 * @param {string} url HTML5 url
11557 this.$$parse = function(url) {
11558 var pathUrl = beginsWith(appBaseNoFile, url);
11559 if (!isString(pathUrl)) {
11560 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11564 parseAppUrl(pathUrl, this);
11566 if (!this.$$path) {
11574 * Compose url and update `absUrl` property
11577 this.$$compose = function() {
11578 var search = toKeyValue(this.$$search),
11579 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11581 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11582 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11585 this.$$parseLinkUrl = function(url, relHref) {
11586 if (relHref && relHref[0] === '#') {
11587 // special case for links to hash fragments:
11588 // keep the old url and only replace the hash fragment
11589 this.hash(relHref.slice(1));
11592 var appUrl, prevAppUrl;
11595 if (isDefined(appUrl = beginsWith(appBase, url))) {
11596 prevAppUrl = appUrl;
11597 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11598 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11600 rewrittenUrl = appBase + prevAppUrl;
11602 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11603 rewrittenUrl = appBaseNoFile + appUrl;
11604 } else if (appBaseNoFile == url + '/') {
11605 rewrittenUrl = appBaseNoFile;
11607 if (rewrittenUrl) {
11608 this.$$parse(rewrittenUrl);
11610 return !!rewrittenUrl;
11616 * LocationHashbangUrl represents url
11617 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11618 * It also serves as the base class for html5 mode fallback on legacy browsers.
11621 * @param {string} appBase application base URL
11622 * @param {string} appBaseNoFile application base URL stripped of any filename
11623 * @param {string} hashPrefix hashbang prefix
11625 function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11627 parseAbsoluteUrl(appBase, this);
11631 * Parse given hashbang url into properties
11632 * @param {string} url Hashbang url
11635 this.$$parse = function(url) {
11636 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11637 var withoutHashUrl;
11639 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11641 // The rest of the url starts with a hash so we have
11642 // got either a hashbang path or a plain hash fragment
11643 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11644 if (isUndefined(withoutHashUrl)) {
11645 // There was no hashbang prefix so we just have a hash fragment
11646 withoutHashUrl = withoutBaseUrl;
11650 // There was no hashbang path nor hash fragment:
11651 // If we are in HTML5 mode we use what is left as the path;
11652 // Otherwise we ignore what is left
11653 if (this.$$html5) {
11654 withoutHashUrl = withoutBaseUrl;
11656 withoutHashUrl = '';
11657 if (isUndefined(withoutBaseUrl)) {
11664 parseAppUrl(withoutHashUrl, this);
11666 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11671 * In Windows, on an anchor node on documents loaded from
11672 * the filesystem, the browser will return a pathname
11673 * prefixed with the drive name ('/C:/path') when a
11674 * pathname without a drive is set:
11675 * * a.setAttribute('href', '/foo')
11676 * * a.pathname === '/C:/foo' //true
11678 * Inside of Angular, we're always using pathnames that
11679 * do not include drive names for routing.
11681 function removeWindowsDriveName(path, url, base) {
11683 Matches paths for file protocol on windows,
11684 such as /C:/foo/bar, and captures only /foo/bar.
11686 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11688 var firstPathSegmentMatch;
11690 //Get the relative path from the input URL.
11691 if (url.indexOf(base) === 0) {
11692 url = url.replace(base, '');
11695 // The input URL intentionally contains a first path segment that ends with a colon.
11696 if (windowsFilePathExp.exec(url)) {
11700 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11701 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11706 * Compose hashbang url and update `absUrl` property
11709 this.$$compose = function() {
11710 var search = toKeyValue(this.$$search),
11711 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11713 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11714 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11717 this.$$parseLinkUrl = function(url, relHref) {
11718 if (stripHash(appBase) == stripHash(url)) {
11728 * LocationHashbangUrl represents url
11729 * This object is exposed as $location service when html5 history api is enabled but the browser
11730 * does not support it.
11733 * @param {string} appBase application base URL
11734 * @param {string} appBaseNoFile application base URL stripped of any filename
11735 * @param {string} hashPrefix hashbang prefix
11737 function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11738 this.$$html5 = true;
11739 LocationHashbangUrl.apply(this, arguments);
11741 this.$$parseLinkUrl = function(url, relHref) {
11742 if (relHref && relHref[0] === '#') {
11743 // special case for links to hash fragments:
11744 // keep the old url and only replace the hash fragment
11745 this.hash(relHref.slice(1));
11752 if (appBase == stripHash(url)) {
11753 rewrittenUrl = url;
11754 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11755 rewrittenUrl = appBase + hashPrefix + appUrl;
11756 } else if (appBaseNoFile === url + '/') {
11757 rewrittenUrl = appBaseNoFile;
11759 if (rewrittenUrl) {
11760 this.$$parse(rewrittenUrl);
11762 return !!rewrittenUrl;
11765 this.$$compose = function() {
11766 var search = toKeyValue(this.$$search),
11767 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11769 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11770 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11771 this.$$absUrl = appBase + hashPrefix + this.$$url;
11777 var locationPrototype = {
11780 * Are we in html5 mode?
11786 * Has any change been replacing?
11793 * @name $location#absUrl
11796 * This method is getter only.
11798 * Return full url representation with all segments encoded according to rules specified in
11799 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11803 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11804 * var absUrl = $location.absUrl();
11805 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11808 * @return {string} full url
11810 absUrl: locationGetter('$$absUrl'),
11814 * @name $location#url
11817 * This method is getter / setter.
11819 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11821 * Change path, search and hash, when called with parameter and return `$location`.
11825 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11826 * var url = $location.url();
11827 * // => "/some/path?foo=bar&baz=xoxo"
11830 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11831 * @return {string} url
11833 url: function(url) {
11834 if (isUndefined(url)) {
11838 var match = PATH_MATCH.exec(url);
11839 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11840 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11841 this.hash(match[5] || '');
11848 * @name $location#protocol
11851 * This method is getter only.
11853 * Return protocol of current url.
11857 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11858 * var protocol = $location.protocol();
11862 * @return {string} protocol of current url
11864 protocol: locationGetter('$$protocol'),
11868 * @name $location#host
11871 * This method is getter only.
11873 * Return host of current url.
11875 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11879 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11880 * var host = $location.host();
11881 * // => "example.com"
11883 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11884 * host = $location.host();
11885 * // => "example.com"
11886 * host = location.host;
11887 * // => "example.com:8080"
11890 * @return {string} host of current url.
11892 host: locationGetter('$$host'),
11896 * @name $location#port
11899 * This method is getter only.
11901 * Return port of current url.
11905 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11906 * var port = $location.port();
11910 * @return {Number} port
11912 port: locationGetter('$$port'),
11916 * @name $location#path
11919 * This method is getter / setter.
11921 * Return path of current url when called without any parameter.
11923 * Change path when called with parameter and return `$location`.
11925 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11926 * if it is missing.
11930 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11931 * var path = $location.path();
11932 * // => "/some/path"
11935 * @param {(string|number)=} path New path
11936 * @return {string} path
11938 path: locationGetterSetter('$$path', function(path) {
11939 path = path !== null ? path.toString() : '';
11940 return path.charAt(0) == '/' ? path : '/' + path;
11945 * @name $location#search
11948 * This method is getter / setter.
11950 * Return search part (as object) of current url when called without any parameter.
11952 * Change search part when called with parameter and return `$location`.
11956 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11957 * var searchObject = $location.search();
11958 * // => {foo: 'bar', baz: 'xoxo'}
11960 * // set foo to 'yipee'
11961 * $location.search('foo', 'yipee');
11962 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11965 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11968 * When called with a single argument the method acts as a setter, setting the `search` component
11969 * of `$location` to the specified value.
11971 * If the argument is a hash object containing an array of values, these values will be encoded
11972 * as duplicate search parameters in the url.
11974 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11975 * will override only a single search property.
11977 * If `paramValue` is an array, it will override the property of the `search` component of
11978 * `$location` specified via the first argument.
11980 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11982 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11983 * value nor trailing equal sign.
11985 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11986 * one or more arguments returns `$location` object itself.
11988 search: function(search, paramValue) {
11989 switch (arguments.length) {
11991 return this.$$search;
11993 if (isString(search) || isNumber(search)) {
11994 search = search.toString();
11995 this.$$search = parseKeyValue(search);
11996 } else if (isObject(search)) {
11997 search = copy(search, {});
11998 // remove object undefined or null properties
11999 forEach(search, function(value, key) {
12000 if (value == null) delete search[key];
12003 this.$$search = search;
12005 throw $locationMinErr('isrcharg',
12006 'The first argument of the `$location#search()` call must be a string or an object.');
12010 if (isUndefined(paramValue) || paramValue === null) {
12011 delete this.$$search[search];
12013 this.$$search[search] = paramValue;
12023 * @name $location#hash
12026 * This method is getter / setter.
12028 * Returns the hash fragment when called without any parameters.
12030 * Changes the hash fragment when called with a parameter and returns `$location`.
12034 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
12035 * var hash = $location.hash();
12036 * // => "hashValue"
12039 * @param {(string|number)=} hash New hash fragment
12040 * @return {string} hash
12042 hash: locationGetterSetter('$$hash', function(hash) {
12043 return hash !== null ? hash.toString() : '';
12048 * @name $location#replace
12051 * If called, all changes to $location during the current `$digest` will replace the current history
12052 * record, instead of adding a new one.
12054 replace: function() {
12055 this.$$replace = true;
12060 forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
12061 Location.prototype = Object.create(locationPrototype);
12065 * @name $location#state
12068 * This method is getter / setter.
12070 * Return the history state object when called without any parameter.
12072 * Change the history state object when called with one parameter and return `$location`.
12073 * The state object is later passed to `pushState` or `replaceState`.
12075 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12076 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12077 * older browsers (like IE9 or Android < 4.0), don't use this method.
12079 * @param {object=} state State object for pushState or replaceState
12080 * @return {object} state
12082 Location.prototype.state = function(state) {
12083 if (!arguments.length) {
12084 return this.$$state;
12087 if (Location !== LocationHtml5Url || !this.$$html5) {
12088 throw $locationMinErr('nostate', 'History API state support is available only ' +
12089 'in HTML5 mode and only in browsers supporting HTML5 History API');
12091 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12092 // but we're changing the $$state reference to $browser.state() during the $digest
12093 // so the modification window is narrow.
12094 this.$$state = isUndefined(state) ? null : state;
12101 function locationGetter(property) {
12102 return function() {
12103 return this[property];
12108 function locationGetterSetter(property, preprocess) {
12109 return function(value) {
12110 if (isUndefined(value)) {
12111 return this[property];
12114 this[property] = preprocess(value);
12126 * @requires $rootElement
12129 * The $location service parses the URL in the browser address bar (based on the
12130 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12131 * available to your application. Changes to the URL in the address bar are reflected into
12132 * $location service and changes to $location are reflected into the browser address bar.
12134 * **The $location service:**
12136 * - Exposes the current URL in the browser address bar, so you can
12137 * - Watch and observe the URL.
12138 * - Change the URL.
12139 * - Synchronizes the URL with the browser when the user
12140 * - Changes the address bar.
12141 * - Clicks the back or forward button (or clicks a History link).
12142 * - Clicks on a link.
12143 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12145 * For more information see {@link guide/$location Developer Guide: Using $location}
12150 * @name $locationProvider
12152 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12154 function $LocationProvider() {
12155 var hashPrefix = '',
12164 * @name $locationProvider#hashPrefix
12166 * @param {string=} prefix Prefix for hash part (containing path and search)
12167 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12169 this.hashPrefix = function(prefix) {
12170 if (isDefined(prefix)) {
12171 hashPrefix = prefix;
12180 * @name $locationProvider#html5Mode
12182 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12183 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12185 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12186 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12187 * support `pushState`.
12188 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12189 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12190 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12191 * See the {@link guide/$location $location guide for more information}
12192 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12193 * enables/disables url rewriting for relative links.
12195 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12197 this.html5Mode = function(mode) {
12198 if (isBoolean(mode)) {
12199 html5Mode.enabled = mode;
12201 } else if (isObject(mode)) {
12203 if (isBoolean(mode.enabled)) {
12204 html5Mode.enabled = mode.enabled;
12207 if (isBoolean(mode.requireBase)) {
12208 html5Mode.requireBase = mode.requireBase;
12211 if (isBoolean(mode.rewriteLinks)) {
12212 html5Mode.rewriteLinks = mode.rewriteLinks;
12223 * @name $location#$locationChangeStart
12224 * @eventType broadcast on root scope
12226 * Broadcasted before a URL will change.
12228 * This change can be prevented by calling
12229 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12230 * details about event object. Upon successful change
12231 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12233 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12234 * the browser supports the HTML5 History API.
12236 * @param {Object} angularEvent Synthetic event object.
12237 * @param {string} newUrl New URL
12238 * @param {string=} oldUrl URL that was before it was changed.
12239 * @param {string=} newState New history state object
12240 * @param {string=} oldState History state object that was before it was changed.
12245 * @name $location#$locationChangeSuccess
12246 * @eventType broadcast on root scope
12248 * Broadcasted after a URL was changed.
12250 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12251 * the browser supports the HTML5 History API.
12253 * @param {Object} angularEvent Synthetic event object.
12254 * @param {string} newUrl New URL
12255 * @param {string=} oldUrl URL that was before it was changed.
12256 * @param {string=} newState New history state object
12257 * @param {string=} oldState History state object that was before it was changed.
12260 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12261 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12264 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12265 initialUrl = $browser.url(),
12268 if (html5Mode.enabled) {
12269 if (!baseHref && html5Mode.requireBase) {
12270 throw $locationMinErr('nobase',
12271 "$location in HTML5 mode requires a <base> tag to be present!");
12273 appBase = serverBase(initialUrl) + (baseHref || '/');
12274 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12276 appBase = stripHash(initialUrl);
12277 LocationMode = LocationHashbangUrl;
12279 var appBaseNoFile = stripFile(appBase);
12281 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12282 $location.$$parseLinkUrl(initialUrl, initialUrl);
12284 $location.$$state = $browser.state();
12286 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12288 function setBrowserUrlWithFallback(url, replace, state) {
12289 var oldUrl = $location.url();
12290 var oldState = $location.$$state;
12292 $browser.url(url, replace, state);
12294 // Make sure $location.state() returns referentially identical (not just deeply equal)
12295 // state object; this makes possible quick checking if the state changed in the digest
12296 // loop. Checking deep equality would be too expensive.
12297 $location.$$state = $browser.state();
12299 // Restore old values if pushState fails
12300 $location.url(oldUrl);
12301 $location.$$state = oldState;
12307 $rootElement.on('click', function(event) {
12308 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12309 // currently we open nice url link and redirect then
12311 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12313 var elm = jqLite(event.target);
12315 // traverse the DOM up to find first A tag
12316 while (nodeName_(elm[0]) !== 'a') {
12317 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12318 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12321 var absHref = elm.prop('href');
12322 // get the actual href attribute - see
12323 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12324 var relHref = elm.attr('href') || elm.attr('xlink:href');
12326 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12327 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12329 absHref = urlResolve(absHref.animVal).href;
12332 // Ignore when url is started with javascript: or mailto:
12333 if (IGNORE_URI_REGEXP.test(absHref)) return;
12335 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12336 if ($location.$$parseLinkUrl(absHref, relHref)) {
12337 // We do a preventDefault for all urls that are part of the angular application,
12338 // in html5mode and also without, so that we are able to abort navigation without
12339 // getting double entries in the location history.
12340 event.preventDefault();
12341 // update location manually
12342 if ($location.absUrl() != $browser.url()) {
12343 $rootScope.$apply();
12344 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12345 $window.angular['ff-684208-preventDefault'] = true;
12352 // rewrite hashbang url <> html5 url
12353 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12354 $browser.url($location.absUrl(), true);
12357 var initializing = true;
12359 // update $location when $browser url changes
12360 $browser.onUrlChange(function(newUrl, newState) {
12362 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12363 // If we are navigating outside of the app then force a reload
12364 $window.location.href = newUrl;
12368 $rootScope.$evalAsync(function() {
12369 var oldUrl = $location.absUrl();
12370 var oldState = $location.$$state;
12371 var defaultPrevented;
12372 newUrl = trimEmptyHash(newUrl);
12373 $location.$$parse(newUrl);
12374 $location.$$state = newState;
12376 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12377 newState, oldState).defaultPrevented;
12379 // if the location was changed by a `$locationChangeStart` handler then stop
12380 // processing this location change
12381 if ($location.absUrl() !== newUrl) return;
12383 if (defaultPrevented) {
12384 $location.$$parse(oldUrl);
12385 $location.$$state = oldState;
12386 setBrowserUrlWithFallback(oldUrl, false, oldState);
12388 initializing = false;
12389 afterLocationChange(oldUrl, oldState);
12392 if (!$rootScope.$$phase) $rootScope.$digest();
12396 $rootScope.$watch(function $locationWatch() {
12397 var oldUrl = trimEmptyHash($browser.url());
12398 var newUrl = trimEmptyHash($location.absUrl());
12399 var oldState = $browser.state();
12400 var currentReplace = $location.$$replace;
12401 var urlOrStateChanged = oldUrl !== newUrl ||
12402 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12404 if (initializing || urlOrStateChanged) {
12405 initializing = false;
12407 $rootScope.$evalAsync(function() {
12408 var newUrl = $location.absUrl();
12409 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12410 $location.$$state, oldState).defaultPrevented;
12412 // if the location was changed by a `$locationChangeStart` handler then stop
12413 // processing this location change
12414 if ($location.absUrl() !== newUrl) return;
12416 if (defaultPrevented) {
12417 $location.$$parse(oldUrl);
12418 $location.$$state = oldState;
12420 if (urlOrStateChanged) {
12421 setBrowserUrlWithFallback(newUrl, currentReplace,
12422 oldState === $location.$$state ? null : $location.$$state);
12424 afterLocationChange(oldUrl, oldState);
12429 $location.$$replace = false;
12431 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12432 // there is a change
12437 function afterLocationChange(oldUrl, oldState) {
12438 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12439 $location.$$state, oldState);
12447 * @requires $window
12450 * Simple service for logging. Default implementation safely writes the message
12451 * into the browser's console (if present).
12453 * The main purpose of this service is to simplify debugging and troubleshooting.
12455 * The default is to log `debug` messages. You can use
12456 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12459 <example module="logExample">
12460 <file name="script.js">
12461 angular.module('logExample', [])
12462 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12463 $scope.$log = $log;
12464 $scope.message = 'Hello World!';
12467 <file name="index.html">
12468 <div ng-controller="LogController">
12469 <p>Reload this page with open console, enter text and hit the log button...</p>
12471 <input type="text" ng-model="message" /></label>
12472 <button ng-click="$log.log(message)">log</button>
12473 <button ng-click="$log.warn(message)">warn</button>
12474 <button ng-click="$log.info(message)">info</button>
12475 <button ng-click="$log.error(message)">error</button>
12476 <button ng-click="$log.debug(message)">debug</button>
12484 * @name $logProvider
12486 * Use the `$logProvider` to configure how the application logs messages
12488 function $LogProvider() {
12494 * @name $logProvider#debugEnabled
12496 * @param {boolean=} flag enable or disable debug level messages
12497 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12499 this.debugEnabled = function(flag) {
12500 if (isDefined(flag)) {
12508 this.$get = ['$window', function($window) {
12515 * Write a log message
12517 log: consoleLog('log'),
12524 * Write an information message
12526 info: consoleLog('info'),
12533 * Write a warning message
12535 warn: consoleLog('warn'),
12542 * Write an error message
12544 error: consoleLog('error'),
12551 * Write a debug message
12553 debug: (function() {
12554 var fn = consoleLog('debug');
12556 return function() {
12558 fn.apply(self, arguments);
12564 function formatError(arg) {
12565 if (arg instanceof Error) {
12567 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12568 ? 'Error: ' + arg.message + '\n' + arg.stack
12570 } else if (arg.sourceURL) {
12571 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12577 function consoleLog(type) {
12578 var console = $window.console || {},
12579 logFn = console[type] || console.log || noop,
12582 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12583 // The reason behind this is that console.log has type "object" in IE8...
12585 hasApply = !!logFn.apply;
12589 return function() {
12591 forEach(arguments, function(arg) {
12592 args.push(formatError(arg));
12594 return logFn.apply(console, args);
12598 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12599 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12600 return function(arg1, arg2) {
12601 logFn(arg1, arg2 == null ? '' : arg2);
12607 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12608 * Any commits to this file should be reviewed with security in mind. *
12609 * Changes to this file can potentially create security vulnerabilities. *
12610 * An approval from 2 Core members with history of modifying *
12611 * this file is required. *
12613 * Does the change somehow allow for arbitrary javascript to be executed? *
12614 * Or allows for someone to change the prototype of built-in objects? *
12615 * Or gives undesired access to variables likes document or window? *
12616 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12618 var $parseMinErr = minErr('$parse');
12620 // Sandboxing Angular Expressions
12621 // ------------------------------
12622 // Angular expressions are generally considered safe because these expressions only have direct
12623 // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12624 // obtaining a reference to native JS functions such as the Function constructor.
12626 // As an example, consider the following Angular expression:
12628 // {}.toString.constructor('alert("evil JS code")')
12630 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12631 // against the expression language, but not to prevent exploits that were enabled by exposing
12632 // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12633 // practice and therefore we are not even trying to protect against interaction with an object
12634 // explicitly exposed in this way.
12636 // In general, it is not possible to access a Window object from an angular expression unless a
12637 // window or some DOM object that has a reference to window is published onto a Scope.
12638 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12641 // See https://docs.angularjs.org/guide/security
12644 function ensureSafeMemberName(name, fullExpression) {
12645 if (name === "__defineGetter__" || name === "__defineSetter__"
12646 || name === "__lookupGetter__" || name === "__lookupSetter__"
12647 || name === "__proto__") {
12648 throw $parseMinErr('isecfld',
12649 'Attempting to access a disallowed field in Angular expressions! '
12650 + 'Expression: {0}', fullExpression);
12655 function getStringValue(name, fullExpression) {
12656 // From the JavaScript docs:
12657 // Property names must be strings. This means that non-string objects cannot be used
12658 // as keys in an object. Any non-string object, including a number, is typecasted
12659 // into a string via the toString method.
12661 // So, to ensure that we are checking the same `name` that JavaScript would use,
12662 // we cast it to a string, if possible.
12663 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12664 // this is, this will handle objects that misbehave.
12666 if (!isString(name)) {
12667 throw $parseMinErr('iseccst',
12668 'Cannot convert object to primitive value! '
12669 + 'Expression: {0}', fullExpression);
12674 function ensureSafeObject(obj, fullExpression) {
12675 // nifty check if obj is Function that is fast and works across iframes and other contexts
12677 if (obj.constructor === obj) {
12678 throw $parseMinErr('isecfn',
12679 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12681 } else if (// isWindow(obj)
12682 obj.window === obj) {
12683 throw $parseMinErr('isecwindow',
12684 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12686 } else if (// isElement(obj)
12687 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12688 throw $parseMinErr('isecdom',
12689 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12691 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12693 throw $parseMinErr('isecobj',
12694 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12701 var CALL = Function.prototype.call;
12702 var APPLY = Function.prototype.apply;
12703 var BIND = Function.prototype.bind;
12705 function ensureSafeFunction(obj, fullExpression) {
12707 if (obj.constructor === obj) {
12708 throw $parseMinErr('isecfn',
12709 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12711 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12712 throw $parseMinErr('isecff',
12713 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12719 function ensureSafeAssignContext(obj, fullExpression) {
12721 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12722 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12723 throw $parseMinErr('isecaf',
12724 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12729 var OPERATORS = createMap();
12730 forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12731 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12734 /////////////////////////////////////////
12740 var Lexer = function(options) {
12741 this.options = options;
12744 Lexer.prototype = {
12745 constructor: Lexer,
12747 lex: function(text) {
12752 while (this.index < this.text.length) {
12753 var ch = this.text.charAt(this.index);
12754 if (ch === '"' || ch === "'") {
12755 this.readString(ch);
12756 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12758 } else if (this.isIdent(ch)) {
12760 } else if (this.is(ch, '(){}[].,;:?')) {
12761 this.tokens.push({index: this.index, text: ch});
12763 } else if (this.isWhitespace(ch)) {
12766 var ch2 = ch + this.peek();
12767 var ch3 = ch2 + this.peek(2);
12768 var op1 = OPERATORS[ch];
12769 var op2 = OPERATORS[ch2];
12770 var op3 = OPERATORS[ch3];
12771 if (op1 || op2 || op3) {
12772 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12773 this.tokens.push({index: this.index, text: token, operator: true});
12774 this.index += token.length;
12776 this.throwError('Unexpected next character ', this.index, this.index + 1);
12780 return this.tokens;
12783 is: function(ch, chars) {
12784 return chars.indexOf(ch) !== -1;
12787 peek: function(i) {
12789 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12792 isNumber: function(ch) {
12793 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12796 isWhitespace: function(ch) {
12797 // IE treats non-breaking space as \u00A0
12798 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12799 ch === '\n' || ch === '\v' || ch === '\u00A0');
12802 isIdent: function(ch) {
12803 return ('a' <= ch && ch <= 'z' ||
12804 'A' <= ch && ch <= 'Z' ||
12805 '_' === ch || ch === '$');
12808 isExpOperator: function(ch) {
12809 return (ch === '-' || ch === '+' || this.isNumber(ch));
12812 throwError: function(error, start, end) {
12813 end = end || this.index;
12814 var colStr = (isDefined(start)
12815 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12817 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12818 error, colStr, this.text);
12821 readNumber: function() {
12823 var start = this.index;
12824 while (this.index < this.text.length) {
12825 var ch = lowercase(this.text.charAt(this.index));
12826 if (ch == '.' || this.isNumber(ch)) {
12829 var peekCh = this.peek();
12830 if (ch == 'e' && this.isExpOperator(peekCh)) {
12832 } else if (this.isExpOperator(ch) &&
12833 peekCh && this.isNumber(peekCh) &&
12834 number.charAt(number.length - 1) == 'e') {
12836 } else if (this.isExpOperator(ch) &&
12837 (!peekCh || !this.isNumber(peekCh)) &&
12838 number.charAt(number.length - 1) == 'e') {
12839 this.throwError('Invalid exponent');
12850 value: Number(number)
12854 readIdent: function() {
12855 var start = this.index;
12856 while (this.index < this.text.length) {
12857 var ch = this.text.charAt(this.index);
12858 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12865 text: this.text.slice(start, this.index),
12870 readString: function(quote) {
12871 var start = this.index;
12874 var rawString = quote;
12875 var escape = false;
12876 while (this.index < this.text.length) {
12877 var ch = this.text.charAt(this.index);
12881 var hex = this.text.substring(this.index + 1, this.index + 5);
12882 if (!hex.match(/[\da-f]{4}/i)) {
12883 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12886 string += String.fromCharCode(parseInt(hex, 16));
12888 var rep = ESCAPE[ch];
12889 string = string + (rep || ch);
12892 } else if (ch === '\\') {
12894 } else if (ch === quote) {
12908 this.throwError('Unterminated quote', start);
12912 var AST = function(lexer, options) {
12913 this.lexer = lexer;
12914 this.options = options;
12917 AST.Program = 'Program';
12918 AST.ExpressionStatement = 'ExpressionStatement';
12919 AST.AssignmentExpression = 'AssignmentExpression';
12920 AST.ConditionalExpression = 'ConditionalExpression';
12921 AST.LogicalExpression = 'LogicalExpression';
12922 AST.BinaryExpression = 'BinaryExpression';
12923 AST.UnaryExpression = 'UnaryExpression';
12924 AST.CallExpression = 'CallExpression';
12925 AST.MemberExpression = 'MemberExpression';
12926 AST.Identifier = 'Identifier';
12927 AST.Literal = 'Literal';
12928 AST.ArrayExpression = 'ArrayExpression';
12929 AST.Property = 'Property';
12930 AST.ObjectExpression = 'ObjectExpression';
12931 AST.ThisExpression = 'ThisExpression';
12933 // Internal use only
12934 AST.NGValueParameter = 'NGValueParameter';
12937 ast: function(text) {
12939 this.tokens = this.lexer.lex(text);
12941 var value = this.program();
12943 if (this.tokens.length !== 0) {
12944 this.throwError('is an unexpected token', this.tokens[0]);
12950 program: function() {
12953 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12954 body.push(this.expressionStatement());
12955 if (!this.expect(';')) {
12956 return { type: AST.Program, body: body};
12961 expressionStatement: function() {
12962 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12965 filterChain: function() {
12966 var left = this.expression();
12968 while ((token = this.expect('|'))) {
12969 left = this.filter(left);
12974 expression: function() {
12975 return this.assignment();
12978 assignment: function() {
12979 var result = this.ternary();
12980 if (this.expect('=')) {
12981 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12986 ternary: function() {
12987 var test = this.logicalOR();
12990 if (this.expect('?')) {
12991 alternate = this.expression();
12992 if (this.consume(':')) {
12993 consequent = this.expression();
12994 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
13000 logicalOR: function() {
13001 var left = this.logicalAND();
13002 while (this.expect('||')) {
13003 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
13008 logicalAND: function() {
13009 var left = this.equality();
13010 while (this.expect('&&')) {
13011 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
13016 equality: function() {
13017 var left = this.relational();
13019 while ((token = this.expect('==','!=','===','!=='))) {
13020 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
13025 relational: function() {
13026 var left = this.additive();
13028 while ((token = this.expect('<', '>', '<=', '>='))) {
13029 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
13034 additive: function() {
13035 var left = this.multiplicative();
13037 while ((token = this.expect('+','-'))) {
13038 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
13043 multiplicative: function() {
13044 var left = this.unary();
13046 while ((token = this.expect('*','/','%'))) {
13047 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
13052 unary: function() {
13054 if ((token = this.expect('+', '-', '!'))) {
13055 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
13057 return this.primary();
13061 primary: function() {
13063 if (this.expect('(')) {
13064 primary = this.filterChain();
13066 } else if (this.expect('[')) {
13067 primary = this.arrayDeclaration();
13068 } else if (this.expect('{')) {
13069 primary = this.object();
13070 } else if (this.constants.hasOwnProperty(this.peek().text)) {
13071 primary = copy(this.constants[this.consume().text]);
13072 } else if (this.peek().identifier) {
13073 primary = this.identifier();
13074 } else if (this.peek().constant) {
13075 primary = this.constant();
13077 this.throwError('not a primary expression', this.peek());
13081 while ((next = this.expect('(', '[', '.'))) {
13082 if (next.text === '(') {
13083 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13085 } else if (next.text === '[') {
13086 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13088 } else if (next.text === '.') {
13089 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13091 this.throwError('IMPOSSIBLE');
13097 filter: function(baseExpression) {
13098 var args = [baseExpression];
13099 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13101 while (this.expect(':')) {
13102 args.push(this.expression());
13108 parseArguments: function() {
13110 if (this.peekToken().text !== ')') {
13112 args.push(this.expression());
13113 } while (this.expect(','));
13118 identifier: function() {
13119 var token = this.consume();
13120 if (!token.identifier) {
13121 this.throwError('is not a valid identifier', token);
13123 return { type: AST.Identifier, name: token.text };
13126 constant: function() {
13127 // TODO check that it is a constant
13128 return { type: AST.Literal, value: this.consume().value };
13131 arrayDeclaration: function() {
13133 if (this.peekToken().text !== ']') {
13135 if (this.peek(']')) {
13136 // Support trailing commas per ES5.1.
13139 elements.push(this.expression());
13140 } while (this.expect(','));
13144 return { type: AST.ArrayExpression, elements: elements };
13147 object: function() {
13148 var properties = [], property;
13149 if (this.peekToken().text !== '}') {
13151 if (this.peek('}')) {
13152 // Support trailing commas per ES5.1.
13155 property = {type: AST.Property, kind: 'init'};
13156 if (this.peek().constant) {
13157 property.key = this.constant();
13158 } else if (this.peek().identifier) {
13159 property.key = this.identifier();
13161 this.throwError("invalid key", this.peek());
13164 property.value = this.expression();
13165 properties.push(property);
13166 } while (this.expect(','));
13170 return {type: AST.ObjectExpression, properties: properties };
13173 throwError: function(msg, token) {
13174 throw $parseMinErr('syntax',
13175 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13176 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13179 consume: function(e1) {
13180 if (this.tokens.length === 0) {
13181 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13184 var token = this.expect(e1);
13186 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13191 peekToken: function() {
13192 if (this.tokens.length === 0) {
13193 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13195 return this.tokens[0];
13198 peek: function(e1, e2, e3, e4) {
13199 return this.peekAhead(0, e1, e2, e3, e4);
13202 peekAhead: function(i, e1, e2, e3, e4) {
13203 if (this.tokens.length > i) {
13204 var token = this.tokens[i];
13205 var t = token.text;
13206 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13207 (!e1 && !e2 && !e3 && !e4)) {
13214 expect: function(e1, e2, e3, e4) {
13215 var token = this.peek(e1, e2, e3, e4);
13217 this.tokens.shift();
13224 /* `undefined` is not a constant, it is an identifier,
13225 * but using it as an identifier is not supported
13228 'true': { type: AST.Literal, value: true },
13229 'false': { type: AST.Literal, value: false },
13230 'null': { type: AST.Literal, value: null },
13231 'undefined': {type: AST.Literal, value: undefined },
13232 'this': {type: AST.ThisExpression }
13236 function ifDefined(v, d) {
13237 return typeof v !== 'undefined' ? v : d;
13240 function plusFn(l, r) {
13241 if (typeof l === 'undefined') return r;
13242 if (typeof r === 'undefined') return l;
13246 function isStateless($filter, filterName) {
13247 var fn = $filter(filterName);
13248 return !fn.$stateful;
13251 function findConstantAndWatchExpressions(ast, $filter) {
13254 switch (ast.type) {
13256 allConstants = true;
13257 forEach(ast.body, function(expr) {
13258 findConstantAndWatchExpressions(expr.expression, $filter);
13259 allConstants = allConstants && expr.expression.constant;
13261 ast.constant = allConstants;
13264 ast.constant = true;
13267 case AST.UnaryExpression:
13268 findConstantAndWatchExpressions(ast.argument, $filter);
13269 ast.constant = ast.argument.constant;
13270 ast.toWatch = ast.argument.toWatch;
13272 case AST.BinaryExpression:
13273 findConstantAndWatchExpressions(ast.left, $filter);
13274 findConstantAndWatchExpressions(ast.right, $filter);
13275 ast.constant = ast.left.constant && ast.right.constant;
13276 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13278 case AST.LogicalExpression:
13279 findConstantAndWatchExpressions(ast.left, $filter);
13280 findConstantAndWatchExpressions(ast.right, $filter);
13281 ast.constant = ast.left.constant && ast.right.constant;
13282 ast.toWatch = ast.constant ? [] : [ast];
13284 case AST.ConditionalExpression:
13285 findConstantAndWatchExpressions(ast.test, $filter);
13286 findConstantAndWatchExpressions(ast.alternate, $filter);
13287 findConstantAndWatchExpressions(ast.consequent, $filter);
13288 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13289 ast.toWatch = ast.constant ? [] : [ast];
13291 case AST.Identifier:
13292 ast.constant = false;
13293 ast.toWatch = [ast];
13295 case AST.MemberExpression:
13296 findConstantAndWatchExpressions(ast.object, $filter);
13297 if (ast.computed) {
13298 findConstantAndWatchExpressions(ast.property, $filter);
13300 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13301 ast.toWatch = [ast];
13303 case AST.CallExpression:
13304 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13306 forEach(ast.arguments, function(expr) {
13307 findConstantAndWatchExpressions(expr, $filter);
13308 allConstants = allConstants && expr.constant;
13309 if (!expr.constant) {
13310 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13313 ast.constant = allConstants;
13314 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13316 case AST.AssignmentExpression:
13317 findConstantAndWatchExpressions(ast.left, $filter);
13318 findConstantAndWatchExpressions(ast.right, $filter);
13319 ast.constant = ast.left.constant && ast.right.constant;
13320 ast.toWatch = [ast];
13322 case AST.ArrayExpression:
13323 allConstants = true;
13325 forEach(ast.elements, function(expr) {
13326 findConstantAndWatchExpressions(expr, $filter);
13327 allConstants = allConstants && expr.constant;
13328 if (!expr.constant) {
13329 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13332 ast.constant = allConstants;
13333 ast.toWatch = argsToWatch;
13335 case AST.ObjectExpression:
13336 allConstants = true;
13338 forEach(ast.properties, function(property) {
13339 findConstantAndWatchExpressions(property.value, $filter);
13340 allConstants = allConstants && property.value.constant;
13341 if (!property.value.constant) {
13342 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13345 ast.constant = allConstants;
13346 ast.toWatch = argsToWatch;
13348 case AST.ThisExpression:
13349 ast.constant = false;
13355 function getInputs(body) {
13356 if (body.length != 1) return;
13357 var lastExpression = body[0].expression;
13358 var candidate = lastExpression.toWatch;
13359 if (candidate.length !== 1) return candidate;
13360 return candidate[0] !== lastExpression ? candidate : undefined;
13363 function isAssignable(ast) {
13364 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13367 function assignableAST(ast) {
13368 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13369 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13373 function isLiteral(ast) {
13374 return ast.body.length === 0 ||
13375 ast.body.length === 1 && (
13376 ast.body[0].expression.type === AST.Literal ||
13377 ast.body[0].expression.type === AST.ArrayExpression ||
13378 ast.body[0].expression.type === AST.ObjectExpression);
13381 function isConstant(ast) {
13382 return ast.constant;
13385 function ASTCompiler(astBuilder, $filter) {
13386 this.astBuilder = astBuilder;
13387 this.$filter = $filter;
13390 ASTCompiler.prototype = {
13391 compile: function(expression, expensiveChecks) {
13393 var ast = this.astBuilder.ast(expression);
13397 expensiveChecks: expensiveChecks,
13398 fn: {vars: [], body: [], own: {}},
13399 assign: {vars: [], body: [], own: {}},
13402 findConstantAndWatchExpressions(ast, self.$filter);
13405 this.stage = 'assign';
13406 if ((assignable = assignableAST(ast))) {
13407 this.state.computing = 'assign';
13408 var result = this.nextId();
13409 this.recurse(assignable, result);
13410 this.return_(result);
13411 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13413 var toWatch = getInputs(ast.body);
13414 self.stage = 'inputs';
13415 forEach(toWatch, function(watch, key) {
13416 var fnKey = 'fn' + key;
13417 self.state[fnKey] = {vars: [], body: [], own: {}};
13418 self.state.computing = fnKey;
13419 var intoId = self.nextId();
13420 self.recurse(watch, intoId);
13421 self.return_(intoId);
13422 self.state.inputs.push(fnKey);
13423 watch.watchId = key;
13425 this.state.computing = 'fn';
13426 this.stage = 'main';
13429 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13430 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13431 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13432 this.filterPrefix() +
13433 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13439 var fn = (new Function('$filter',
13440 'ensureSafeMemberName',
13441 'ensureSafeObject',
13442 'ensureSafeFunction',
13444 'ensureSafeAssignContext',
13450 ensureSafeMemberName,
13452 ensureSafeFunction,
13454 ensureSafeAssignContext,
13459 this.state = this.stage = undefined;
13460 fn.literal = isLiteral(ast);
13461 fn.constant = isConstant(ast);
13469 watchFns: function() {
13471 var fns = this.state.inputs;
13473 forEach(fns, function(name) {
13474 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13477 result.push('fn.inputs=[' + fns.join(',') + '];');
13479 return result.join('');
13482 generateFunction: function(name, params) {
13483 return 'function(' + params + '){' +
13484 this.varsPrefix(name) +
13489 filterPrefix: function() {
13492 forEach(this.state.filters, function(id, filter) {
13493 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13495 if (parts.length) return 'var ' + parts.join(',') + ';';
13499 varsPrefix: function(section) {
13500 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13503 body: function(section) {
13504 return this.state[section].body.join('');
13507 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13508 var left, right, self = this, args, expression;
13509 recursionFn = recursionFn || noop;
13510 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13511 intoId = intoId || this.nextId();
13513 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13514 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13518 switch (ast.type) {
13520 forEach(ast.body, function(expression, pos) {
13521 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13522 if (pos !== ast.body.length - 1) {
13523 self.current().body.push(right, ';');
13525 self.return_(right);
13530 expression = this.escape(ast.value);
13531 this.assign(intoId, expression);
13532 recursionFn(expression);
13534 case AST.UnaryExpression:
13535 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13536 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13537 this.assign(intoId, expression);
13538 recursionFn(expression);
13540 case AST.BinaryExpression:
13541 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13542 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13543 if (ast.operator === '+') {
13544 expression = this.plus(left, right);
13545 } else if (ast.operator === '-') {
13546 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13548 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13550 this.assign(intoId, expression);
13551 recursionFn(expression);
13553 case AST.LogicalExpression:
13554 intoId = intoId || this.nextId();
13555 self.recurse(ast.left, intoId);
13556 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13557 recursionFn(intoId);
13559 case AST.ConditionalExpression:
13560 intoId = intoId || this.nextId();
13561 self.recurse(ast.test, intoId);
13562 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13563 recursionFn(intoId);
13565 case AST.Identifier:
13566 intoId = intoId || this.nextId();
13568 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13569 nameId.computed = false;
13570 nameId.name = ast.name;
13572 ensureSafeMemberName(ast.name);
13573 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13575 self.if_(self.stage === 'inputs' || 's', function() {
13576 if (create && create !== 1) {
13578 self.not(self.nonComputedMember('s', ast.name)),
13579 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13581 self.assign(intoId, self.nonComputedMember('s', ast.name));
13583 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13585 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13586 self.addEnsureSafeObject(intoId);
13588 recursionFn(intoId);
13590 case AST.MemberExpression:
13591 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13592 intoId = intoId || this.nextId();
13593 self.recurse(ast.object, left, undefined, function() {
13594 self.if_(self.notNull(left), function() {
13595 if (ast.computed) {
13596 right = self.nextId();
13597 self.recurse(ast.property, right);
13598 self.getStringValue(right);
13599 self.addEnsureSafeMemberName(right);
13600 if (create && create !== 1) {
13601 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13603 expression = self.ensureSafeObject(self.computedMember(left, right));
13604 self.assign(intoId, expression);
13606 nameId.computed = true;
13607 nameId.name = right;
13610 ensureSafeMemberName(ast.property.name);
13611 if (create && create !== 1) {
13612 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13614 expression = self.nonComputedMember(left, ast.property.name);
13615 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13616 expression = self.ensureSafeObject(expression);
13618 self.assign(intoId, expression);
13620 nameId.computed = false;
13621 nameId.name = ast.property.name;
13625 self.assign(intoId, 'undefined');
13627 recursionFn(intoId);
13630 case AST.CallExpression:
13631 intoId = intoId || this.nextId();
13633 right = self.filter(ast.callee.name);
13635 forEach(ast.arguments, function(expr) {
13636 var argument = self.nextId();
13637 self.recurse(expr, argument);
13638 args.push(argument);
13640 expression = right + '(' + args.join(',') + ')';
13641 self.assign(intoId, expression);
13642 recursionFn(intoId);
13644 right = self.nextId();
13647 self.recurse(ast.callee, right, left, function() {
13648 self.if_(self.notNull(right), function() {
13649 self.addEnsureSafeFunction(right);
13650 forEach(ast.arguments, function(expr) {
13651 self.recurse(expr, self.nextId(), undefined, function(argument) {
13652 args.push(self.ensureSafeObject(argument));
13656 if (!self.state.expensiveChecks) {
13657 self.addEnsureSafeObject(left.context);
13659 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13661 expression = right + '(' + args.join(',') + ')';
13663 expression = self.ensureSafeObject(expression);
13664 self.assign(intoId, expression);
13666 self.assign(intoId, 'undefined');
13668 recursionFn(intoId);
13672 case AST.AssignmentExpression:
13673 right = this.nextId();
13675 if (!isAssignable(ast.left)) {
13676 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13678 this.recurse(ast.left, undefined, left, function() {
13679 self.if_(self.notNull(left.context), function() {
13680 self.recurse(ast.right, right);
13681 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13682 self.addEnsureSafeAssignContext(left.context);
13683 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13684 self.assign(intoId, expression);
13685 recursionFn(intoId || expression);
13689 case AST.ArrayExpression:
13691 forEach(ast.elements, function(expr) {
13692 self.recurse(expr, self.nextId(), undefined, function(argument) {
13693 args.push(argument);
13696 expression = '[' + args.join(',') + ']';
13697 this.assign(intoId, expression);
13698 recursionFn(expression);
13700 case AST.ObjectExpression:
13702 forEach(ast.properties, function(property) {
13703 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13704 args.push(self.escape(
13705 property.key.type === AST.Identifier ? property.key.name :
13706 ('' + property.key.value)) +
13710 expression = '{' + args.join(',') + '}';
13711 this.assign(intoId, expression);
13712 recursionFn(expression);
13714 case AST.ThisExpression:
13715 this.assign(intoId, 's');
13718 case AST.NGValueParameter:
13719 this.assign(intoId, 'v');
13725 getHasOwnProperty: function(element, property) {
13726 var key = element + '.' + property;
13727 var own = this.current().own;
13728 if (!own.hasOwnProperty(key)) {
13729 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13734 assign: function(id, value) {
13736 this.current().body.push(id, '=', value, ';');
13740 filter: function(filterName) {
13741 if (!this.state.filters.hasOwnProperty(filterName)) {
13742 this.state.filters[filterName] = this.nextId(true);
13744 return this.state.filters[filterName];
13747 ifDefined: function(id, defaultValue) {
13748 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13751 plus: function(left, right) {
13752 return 'plus(' + left + ',' + right + ')';
13755 return_: function(id) {
13756 this.current().body.push('return ', id, ';');
13759 if_: function(test, alternate, consequent) {
13760 if (test === true) {
13763 var body = this.current().body;
13764 body.push('if(', test, '){');
13768 body.push('else{');
13775 not: function(expression) {
13776 return '!(' + expression + ')';
13779 notNull: function(expression) {
13780 return expression + '!=null';
13783 nonComputedMember: function(left, right) {
13784 return left + '.' + right;
13787 computedMember: function(left, right) {
13788 return left + '[' + right + ']';
13791 member: function(left, right, computed) {
13792 if (computed) return this.computedMember(left, right);
13793 return this.nonComputedMember(left, right);
13796 addEnsureSafeObject: function(item) {
13797 this.current().body.push(this.ensureSafeObject(item), ';');
13800 addEnsureSafeMemberName: function(item) {
13801 this.current().body.push(this.ensureSafeMemberName(item), ';');
13804 addEnsureSafeFunction: function(item) {
13805 this.current().body.push(this.ensureSafeFunction(item), ';');
13808 addEnsureSafeAssignContext: function(item) {
13809 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13812 ensureSafeObject: function(item) {
13813 return 'ensureSafeObject(' + item + ',text)';
13816 ensureSafeMemberName: function(item) {
13817 return 'ensureSafeMemberName(' + item + ',text)';
13820 ensureSafeFunction: function(item) {
13821 return 'ensureSafeFunction(' + item + ',text)';
13824 getStringValue: function(item) {
13825 this.assign(item, 'getStringValue(' + item + ',text)');
13828 ensureSafeAssignContext: function(item) {
13829 return 'ensureSafeAssignContext(' + item + ',text)';
13832 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13834 return function() {
13835 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13839 lazyAssign: function(id, value) {
13841 return function() {
13842 self.assign(id, value);
13846 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13848 stringEscapeFn: function(c) {
13849 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13852 escape: function(value) {
13853 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13854 if (isNumber(value)) return value.toString();
13855 if (value === true) return 'true';
13856 if (value === false) return 'false';
13857 if (value === null) return 'null';
13858 if (typeof value === 'undefined') return 'undefined';
13860 throw $parseMinErr('esc', 'IMPOSSIBLE');
13863 nextId: function(skip, init) {
13864 var id = 'v' + (this.state.nextId++);
13866 this.current().vars.push(id + (init ? '=' + init : ''));
13871 current: function() {
13872 return this.state[this.state.computing];
13877 function ASTInterpreter(astBuilder, $filter) {
13878 this.astBuilder = astBuilder;
13879 this.$filter = $filter;
13882 ASTInterpreter.prototype = {
13883 compile: function(expression, expensiveChecks) {
13885 var ast = this.astBuilder.ast(expression);
13886 this.expression = expression;
13887 this.expensiveChecks = expensiveChecks;
13888 findConstantAndWatchExpressions(ast, self.$filter);
13891 if ((assignable = assignableAST(ast))) {
13892 assign = this.recurse(assignable);
13894 var toWatch = getInputs(ast.body);
13898 forEach(toWatch, function(watch, key) {
13899 var input = self.recurse(watch);
13900 watch.input = input;
13901 inputs.push(input);
13902 watch.watchId = key;
13905 var expressions = [];
13906 forEach(ast.body, function(expression) {
13907 expressions.push(self.recurse(expression.expression));
13909 var fn = ast.body.length === 0 ? function() {} :
13910 ast.body.length === 1 ? expressions[0] :
13911 function(scope, locals) {
13913 forEach(expressions, function(exp) {
13914 lastValue = exp(scope, locals);
13919 fn.assign = function(scope, value, locals) {
13920 return assign(scope, locals, value);
13924 fn.inputs = inputs;
13926 fn.literal = isLiteral(ast);
13927 fn.constant = isConstant(ast);
13931 recurse: function(ast, context, create) {
13932 var left, right, self = this, args, expression;
13934 return this.inputs(ast.input, ast.watchId);
13936 switch (ast.type) {
13938 return this.value(ast.value, context);
13939 case AST.UnaryExpression:
13940 right = this.recurse(ast.argument);
13941 return this['unary' + ast.operator](right, context);
13942 case AST.BinaryExpression:
13943 left = this.recurse(ast.left);
13944 right = this.recurse(ast.right);
13945 return this['binary' + ast.operator](left, right, context);
13946 case AST.LogicalExpression:
13947 left = this.recurse(ast.left);
13948 right = this.recurse(ast.right);
13949 return this['binary' + ast.operator](left, right, context);
13950 case AST.ConditionalExpression:
13951 return this['ternary?:'](
13952 this.recurse(ast.test),
13953 this.recurse(ast.alternate),
13954 this.recurse(ast.consequent),
13957 case AST.Identifier:
13958 ensureSafeMemberName(ast.name, self.expression);
13959 return self.identifier(ast.name,
13960 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13961 context, create, self.expression);
13962 case AST.MemberExpression:
13963 left = this.recurse(ast.object, false, !!create);
13964 if (!ast.computed) {
13965 ensureSafeMemberName(ast.property.name, self.expression);
13966 right = ast.property.name;
13968 if (ast.computed) right = this.recurse(ast.property);
13969 return ast.computed ?
13970 this.computedMember(left, right, context, create, self.expression) :
13971 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13972 case AST.CallExpression:
13974 forEach(ast.arguments, function(expr) {
13975 args.push(self.recurse(expr));
13977 if (ast.filter) right = this.$filter(ast.callee.name);
13978 if (!ast.filter) right = this.recurse(ast.callee, true);
13979 return ast.filter ?
13980 function(scope, locals, assign, inputs) {
13982 for (var i = 0; i < args.length; ++i) {
13983 values.push(args[i](scope, locals, assign, inputs));
13985 var value = right.apply(undefined, values, inputs);
13986 return context ? {context: undefined, name: undefined, value: value} : value;
13988 function(scope, locals, assign, inputs) {
13989 var rhs = right(scope, locals, assign, inputs);
13991 if (rhs.value != null) {
13992 ensureSafeObject(rhs.context, self.expression);
13993 ensureSafeFunction(rhs.value, self.expression);
13995 for (var i = 0; i < args.length; ++i) {
13996 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
13998 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
14000 return context ? {value: value} : value;
14002 case AST.AssignmentExpression:
14003 left = this.recurse(ast.left, true, 1);
14004 right = this.recurse(ast.right);
14005 return function(scope, locals, assign, inputs) {
14006 var lhs = left(scope, locals, assign, inputs);
14007 var rhs = right(scope, locals, assign, inputs);
14008 ensureSafeObject(lhs.value, self.expression);
14009 ensureSafeAssignContext(lhs.context);
14010 lhs.context[lhs.name] = rhs;
14011 return context ? {value: rhs} : rhs;
14013 case AST.ArrayExpression:
14015 forEach(ast.elements, function(expr) {
14016 args.push(self.recurse(expr));
14018 return function(scope, locals, assign, inputs) {
14020 for (var i = 0; i < args.length; ++i) {
14021 value.push(args[i](scope, locals, assign, inputs));
14023 return context ? {value: value} : value;
14025 case AST.ObjectExpression:
14027 forEach(ast.properties, function(property) {
14028 args.push({key: property.key.type === AST.Identifier ?
14029 property.key.name :
14030 ('' + property.key.value),
14031 value: self.recurse(property.value)
14034 return function(scope, locals, assign, inputs) {
14036 for (var i = 0; i < args.length; ++i) {
14037 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
14039 return context ? {value: value} : value;
14041 case AST.ThisExpression:
14042 return function(scope) {
14043 return context ? {value: scope} : scope;
14045 case AST.NGValueParameter:
14046 return function(scope, locals, assign, inputs) {
14047 return context ? {value: assign} : assign;
14052 'unary+': function(argument, context) {
14053 return function(scope, locals, assign, inputs) {
14054 var arg = argument(scope, locals, assign, inputs);
14055 if (isDefined(arg)) {
14060 return context ? {value: arg} : arg;
14063 'unary-': function(argument, context) {
14064 return function(scope, locals, assign, inputs) {
14065 var arg = argument(scope, locals, assign, inputs);
14066 if (isDefined(arg)) {
14071 return context ? {value: arg} : arg;
14074 'unary!': function(argument, context) {
14075 return function(scope, locals, assign, inputs) {
14076 var arg = !argument(scope, locals, assign, inputs);
14077 return context ? {value: arg} : arg;
14080 'binary+': function(left, right, context) {
14081 return function(scope, locals, assign, inputs) {
14082 var lhs = left(scope, locals, assign, inputs);
14083 var rhs = right(scope, locals, assign, inputs);
14084 var arg = plusFn(lhs, rhs);
14085 return context ? {value: arg} : arg;
14088 'binary-': function(left, right, context) {
14089 return function(scope, locals, assign, inputs) {
14090 var lhs = left(scope, locals, assign, inputs);
14091 var rhs = right(scope, locals, assign, inputs);
14092 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14093 return context ? {value: arg} : arg;
14096 'binary*': function(left, right, context) {
14097 return function(scope, locals, assign, inputs) {
14098 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14099 return context ? {value: arg} : arg;
14102 'binary/': function(left, right, context) {
14103 return function(scope, locals, assign, inputs) {
14104 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14105 return context ? {value: arg} : arg;
14108 'binary%': function(left, right, context) {
14109 return function(scope, locals, assign, inputs) {
14110 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14111 return context ? {value: arg} : arg;
14114 'binary===': function(left, right, context) {
14115 return function(scope, locals, assign, inputs) {
14116 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14117 return context ? {value: arg} : arg;
14120 'binary!==': function(left, right, context) {
14121 return function(scope, locals, assign, inputs) {
14122 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14123 return context ? {value: arg} : arg;
14126 'binary==': function(left, right, context) {
14127 return function(scope, locals, assign, inputs) {
14128 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14129 return context ? {value: arg} : arg;
14132 'binary!=': function(left, right, context) {
14133 return function(scope, locals, assign, inputs) {
14134 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14135 return context ? {value: arg} : arg;
14138 'binary<': function(left, right, context) {
14139 return function(scope, locals, assign, inputs) {
14140 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14141 return context ? {value: arg} : arg;
14144 'binary>': function(left, right, context) {
14145 return function(scope, locals, assign, inputs) {
14146 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14147 return context ? {value: arg} : arg;
14150 'binary<=': function(left, right, context) {
14151 return function(scope, locals, assign, inputs) {
14152 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14153 return context ? {value: arg} : arg;
14156 'binary>=': function(left, right, context) {
14157 return function(scope, locals, assign, inputs) {
14158 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14159 return context ? {value: arg} : arg;
14162 'binary&&': function(left, right, context) {
14163 return function(scope, locals, assign, inputs) {
14164 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14165 return context ? {value: arg} : arg;
14168 'binary||': function(left, right, context) {
14169 return function(scope, locals, assign, inputs) {
14170 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14171 return context ? {value: arg} : arg;
14174 'ternary?:': function(test, alternate, consequent, context) {
14175 return function(scope, locals, assign, inputs) {
14176 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14177 return context ? {value: arg} : arg;
14180 value: function(value, context) {
14181 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14183 identifier: function(name, expensiveChecks, context, create, expression) {
14184 return function(scope, locals, assign, inputs) {
14185 var base = locals && (name in locals) ? locals : scope;
14186 if (create && create !== 1 && base && !(base[name])) {
14189 var value = base ? base[name] : undefined;
14190 if (expensiveChecks) {
14191 ensureSafeObject(value, expression);
14194 return {context: base, name: name, value: value};
14200 computedMember: function(left, right, context, create, expression) {
14201 return function(scope, locals, assign, inputs) {
14202 var lhs = left(scope, locals, assign, inputs);
14206 rhs = right(scope, locals, assign, inputs);
14207 rhs = getStringValue(rhs);
14208 ensureSafeMemberName(rhs, expression);
14209 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14213 ensureSafeObject(value, expression);
14216 return {context: lhs, name: rhs, value: value};
14222 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14223 return function(scope, locals, assign, inputs) {
14224 var lhs = left(scope, locals, assign, inputs);
14225 if (create && create !== 1 && lhs && !(lhs[right])) {
14228 var value = lhs != null ? lhs[right] : undefined;
14229 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14230 ensureSafeObject(value, expression);
14233 return {context: lhs, name: right, value: value};
14239 inputs: function(input, watchId) {
14240 return function(scope, value, locals, inputs) {
14241 if (inputs) return inputs[watchId];
14242 return input(scope, value, locals);
14250 var Parser = function(lexer, $filter, options) {
14251 this.lexer = lexer;
14252 this.$filter = $filter;
14253 this.options = options;
14254 this.ast = new AST(this.lexer);
14255 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14256 new ASTCompiler(this.ast, $filter);
14259 Parser.prototype = {
14260 constructor: Parser,
14262 parse: function(text) {
14263 return this.astCompiler.compile(text, this.options.expensiveChecks);
14267 var getterFnCacheDefault = createMap();
14268 var getterFnCacheExpensive = createMap();
14270 function isPossiblyDangerousMemberName(name) {
14271 return name == 'constructor';
14274 var objectValueOf = Object.prototype.valueOf;
14276 function getValueOf(value) {
14277 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14280 ///////////////////////////////////
14289 * Converts Angular {@link guide/expression expression} into a function.
14292 * var getter = $parse('user.name');
14293 * var setter = getter.assign;
14294 * var context = {user:{name:'angular'}};
14295 * var locals = {user:{name:'local'}};
14297 * expect(getter(context)).toEqual('angular');
14298 * setter(context, 'newValue');
14299 * expect(context.user.name).toEqual('newValue');
14300 * expect(getter(context, locals)).toEqual('local');
14304 * @param {string} expression String expression to compile.
14305 * @returns {function(context, locals)} a function which represents the compiled expression:
14307 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14308 * are evaluated against (typically a scope object).
14309 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14312 * The returned function also has the following properties:
14313 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14315 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14316 * constant literals.
14317 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14318 * set to a function to change its value on the given context.
14325 * @name $parseProvider
14328 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14331 function $ParseProvider() {
14332 var cacheDefault = createMap();
14333 var cacheExpensive = createMap();
14335 this.$get = ['$filter', function($filter) {
14336 var noUnsafeEval = csp().noUnsafeEval;
14337 var $parseOptions = {
14339 expensiveChecks: false
14341 $parseOptionsExpensive = {
14343 expensiveChecks: true
14346 return function $parse(exp, interceptorFn, expensiveChecks) {
14347 var parsedExpression, oneTime, cacheKey;
14349 switch (typeof exp) {
14354 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14355 parsedExpression = cache[cacheKey];
14357 if (!parsedExpression) {
14358 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14360 exp = exp.substring(2);
14362 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14363 var lexer = new Lexer(parseOptions);
14364 var parser = new Parser(lexer, $filter, parseOptions);
14365 parsedExpression = parser.parse(exp);
14366 if (parsedExpression.constant) {
14367 parsedExpression.$$watchDelegate = constantWatchDelegate;
14368 } else if (oneTime) {
14369 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14370 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14371 } else if (parsedExpression.inputs) {
14372 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14374 cache[cacheKey] = parsedExpression;
14376 return addInterceptor(parsedExpression, interceptorFn);
14379 return addInterceptor(exp, interceptorFn);
14386 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14388 if (newValue == null || oldValueOfValue == null) { // null/undefined
14389 return newValue === oldValueOfValue;
14392 if (typeof newValue === 'object') {
14394 // attempt to convert the value to a primitive type
14395 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14396 // be cheaply dirty-checked
14397 newValue = getValueOf(newValue);
14399 if (typeof newValue === 'object') {
14400 // objects/arrays are not supported - deep-watching them would be too expensive
14404 // fall-through to the primitive equality check
14408 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14411 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14412 var inputExpressions = parsedExpression.inputs;
14415 if (inputExpressions.length === 1) {
14416 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14417 inputExpressions = inputExpressions[0];
14418 return scope.$watch(function expressionInputWatch(scope) {
14419 var newInputValue = inputExpressions(scope);
14420 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14421 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14422 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14425 }, listener, objectEquality, prettyPrintExpression);
14428 var oldInputValueOfValues = [];
14429 var oldInputValues = [];
14430 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14431 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14432 oldInputValues[i] = null;
14435 return scope.$watch(function expressionInputsWatch(scope) {
14436 var changed = false;
14438 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14439 var newInputValue = inputExpressions[i](scope);
14440 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14441 oldInputValues[i] = newInputValue;
14442 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14447 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14451 }, listener, objectEquality, prettyPrintExpression);
14454 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14455 var unwatch, lastValue;
14456 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14457 return parsedExpression(scope);
14458 }, function oneTimeListener(value, old, scope) {
14460 if (isFunction(listener)) {
14461 listener.apply(this, arguments);
14463 if (isDefined(value)) {
14464 scope.$$postDigest(function() {
14465 if (isDefined(lastValue)) {
14470 }, objectEquality);
14473 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14474 var unwatch, lastValue;
14475 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14476 return parsedExpression(scope);
14477 }, function oneTimeListener(value, old, scope) {
14479 if (isFunction(listener)) {
14480 listener.call(this, value, old, scope);
14482 if (isAllDefined(value)) {
14483 scope.$$postDigest(function() {
14484 if (isAllDefined(lastValue)) unwatch();
14487 }, objectEquality);
14489 function isAllDefined(value) {
14490 var allDefined = true;
14491 forEach(value, function(val) {
14492 if (!isDefined(val)) allDefined = false;
14498 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14500 return unwatch = scope.$watch(function constantWatch(scope) {
14501 return parsedExpression(scope);
14502 }, function constantListener(value, old, scope) {
14503 if (isFunction(listener)) {
14504 listener.apply(this, arguments);
14507 }, objectEquality);
14510 function addInterceptor(parsedExpression, interceptorFn) {
14511 if (!interceptorFn) return parsedExpression;
14512 var watchDelegate = parsedExpression.$$watchDelegate;
14513 var useInputs = false;
14516 watchDelegate !== oneTimeLiteralWatchDelegate &&
14517 watchDelegate !== oneTimeWatchDelegate;
14519 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14520 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14521 return interceptorFn(value, scope, locals);
14522 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14523 var value = parsedExpression(scope, locals, assign, inputs);
14524 var result = interceptorFn(value, scope, locals);
14525 // we only return the interceptor's result if the
14526 // initial value is defined (for bind-once)
14527 return isDefined(value) ? result : value;
14530 // Propagate $$watchDelegates other then inputsWatchDelegate
14531 if (parsedExpression.$$watchDelegate &&
14532 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14533 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14534 } else if (!interceptorFn.$stateful) {
14535 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14536 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14537 fn.$$watchDelegate = inputsWatchDelegate;
14538 useInputs = !parsedExpression.inputs;
14539 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14550 * @requires $rootScope
14553 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14554 * when they are done processing.
14556 * This is an implementation of promises/deferred objects inspired by
14557 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14559 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14560 * implementations, and the other which resembles ES6 promises to some degree.
14564 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14565 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14566 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14568 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14571 * It can be used like so:
14574 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14575 * // are available in the current lexical scope (they could have been injected or passed in).
14577 * function asyncGreet(name) {
14578 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14579 * return $q(function(resolve, reject) {
14580 * setTimeout(function() {
14581 * if (okToGreet(name)) {
14582 * resolve('Hello, ' + name + '!');
14584 * reject('Greeting ' + name + ' is not allowed.');
14590 * var promise = asyncGreet('Robin Hood');
14591 * promise.then(function(greeting) {
14592 * alert('Success: ' + greeting);
14593 * }, function(reason) {
14594 * alert('Failed: ' + reason);
14598 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14600 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14602 * However, the more traditional CommonJS-style usage is still available, and documented below.
14604 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14605 * interface for interacting with an object that represents the result of an action that is
14606 * performed asynchronously, and may or may not be finished at any given point in time.
14608 * From the perspective of dealing with error handling, deferred and promise APIs are to
14609 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14612 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14613 * // are available in the current lexical scope (they could have been injected or passed in).
14615 * function asyncGreet(name) {
14616 * var deferred = $q.defer();
14618 * setTimeout(function() {
14619 * deferred.notify('About to greet ' + name + '.');
14621 * if (okToGreet(name)) {
14622 * deferred.resolve('Hello, ' + name + '!');
14624 * deferred.reject('Greeting ' + name + ' is not allowed.');
14628 * return deferred.promise;
14631 * var promise = asyncGreet('Robin Hood');
14632 * promise.then(function(greeting) {
14633 * alert('Success: ' + greeting);
14634 * }, function(reason) {
14635 * alert('Failed: ' + reason);
14636 * }, function(update) {
14637 * alert('Got notification: ' + update);
14641 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14642 * comes in the way of guarantees that promise and deferred APIs make, see
14643 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14645 * Additionally the promise api allows for composition that is very hard to do with the
14646 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14647 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14648 * section on serial or parallel joining of promises.
14650 * # The Deferred API
14652 * A new instance of deferred is constructed by calling `$q.defer()`.
14654 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14655 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14660 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14661 * constructed via `$q.reject`, the promise will be rejected instead.
14662 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14663 * resolving it with a rejection constructed via `$q.reject`.
14664 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14665 * multiple times before the promise is either resolved or rejected.
14669 * - promise – `{Promise}` – promise object associated with this deferred.
14672 * # The Promise API
14674 * A new promise instance is created when a deferred instance is created and can be retrieved by
14675 * calling `deferred.promise`.
14677 * The purpose of the promise object is to allow for interested parties to get access to the result
14678 * of the deferred task when it completes.
14682 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14683 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14684 * as soon as the result is available. The callbacks are called with a single argument: the result
14685 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14686 * provide a progress indication, before the promise is resolved or rejected.
14688 * This method *returns a new promise* which is resolved or rejected via the return value of the
14689 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14690 * with the value which is resolved in that promise using
14691 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14692 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14693 * resolved or rejected from the notifyCallback method.
14695 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14697 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14698 * but to do so without modifying the final value. This is useful to release resources or do some
14699 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14700 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14701 * more information.
14703 * # Chaining promises
14705 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14706 * possible to create a chain of promises:
14709 * promiseB = promiseA.then(function(result) {
14710 * return result + 1;
14713 * // promiseB will be resolved immediately after promiseA is resolved and its value
14714 * // will be the result of promiseA incremented by 1
14717 * It is possible to create chains of any length and since a promise can be resolved with another
14718 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14719 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14720 * $http's response interceptors.
14723 * # Differences between Kris Kowal's Q and $q
14725 * There are two main differences:
14727 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14728 * mechanism in angular, which means faster propagation of resolution or rejection into your
14729 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14730 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14731 * all the important functionality needed for common async tasks.
14736 * it('should simulate promise', inject(function($q, $rootScope) {
14737 * var deferred = $q.defer();
14738 * var promise = deferred.promise;
14739 * var resolvedValue;
14741 * promise.then(function(value) { resolvedValue = value; });
14742 * expect(resolvedValue).toBeUndefined();
14744 * // Simulate resolving of promise
14745 * deferred.resolve(123);
14746 * // Note that the 'then' function does not get called synchronously.
14747 * // This is because we want the promise API to always be async, whether or not
14748 * // it got called synchronously or asynchronously.
14749 * expect(resolvedValue).toBeUndefined();
14751 * // Propagate promise resolution to 'then' functions using $apply().
14752 * $rootScope.$apply();
14753 * expect(resolvedValue).toEqual(123);
14757 * @param {function(function, function)} resolver Function which is responsible for resolving or
14758 * rejecting the newly created promise. The first parameter is a function which resolves the
14759 * promise, the second parameter is a function which rejects the promise.
14761 * @returns {Promise} The newly created promise.
14763 function $QProvider() {
14765 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14766 return qFactory(function(callback) {
14767 $rootScope.$evalAsync(callback);
14768 }, $exceptionHandler);
14772 function $$QProvider() {
14773 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14774 return qFactory(function(callback) {
14775 $browser.defer(callback);
14776 }, $exceptionHandler);
14781 * Constructs a promise manager.
14783 * @param {function(function)} nextTick Function for executing functions in the next turn.
14784 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14785 * debugging purposes.
14786 * @returns {object} Promise manager.
14788 function qFactory(nextTick, exceptionHandler) {
14789 var $qMinErr = minErr('$q', TypeError);
14790 function callOnce(self, resolveFn, rejectFn) {
14791 var called = false;
14792 function wrap(fn) {
14793 return function(value) {
14794 if (called) return;
14796 fn.call(self, value);
14800 return [wrap(resolveFn), wrap(rejectFn)];
14805 * @name ng.$q#defer
14809 * Creates a `Deferred` object which represents a task which will finish in the future.
14811 * @returns {Deferred} Returns a new instance of deferred.
14813 var defer = function() {
14814 return new Deferred();
14817 function Promise() {
14818 this.$$state = { status: 0 };
14821 extend(Promise.prototype, {
14822 then: function(onFulfilled, onRejected, progressBack) {
14823 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14826 var result = new Deferred();
14828 this.$$state.pending = this.$$state.pending || [];
14829 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14830 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14832 return result.promise;
14835 "catch": function(callback) {
14836 return this.then(null, callback);
14839 "finally": function(callback, progressBack) {
14840 return this.then(function(value) {
14841 return handleCallback(value, true, callback);
14842 }, function(error) {
14843 return handleCallback(error, false, callback);
14848 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14849 function simpleBind(context, fn) {
14850 return function(value) {
14851 fn.call(context, value);
14855 function processQueue(state) {
14856 var fn, deferred, pending;
14858 pending = state.pending;
14859 state.processScheduled = false;
14860 state.pending = undefined;
14861 for (var i = 0, ii = pending.length; i < ii; ++i) {
14862 deferred = pending[i][0];
14863 fn = pending[i][state.status];
14865 if (isFunction(fn)) {
14866 deferred.resolve(fn(state.value));
14867 } else if (state.status === 1) {
14868 deferred.resolve(state.value);
14870 deferred.reject(state.value);
14873 deferred.reject(e);
14874 exceptionHandler(e);
14879 function scheduleProcessQueue(state) {
14880 if (state.processScheduled || !state.pending) return;
14881 state.processScheduled = true;
14882 nextTick(function() { processQueue(state); });
14885 function Deferred() {
14886 this.promise = new Promise();
14887 //Necessary to support unbound execution :/
14888 this.resolve = simpleBind(this, this.resolve);
14889 this.reject = simpleBind(this, this.reject);
14890 this.notify = simpleBind(this, this.notify);
14893 extend(Deferred.prototype, {
14894 resolve: function(val) {
14895 if (this.promise.$$state.status) return;
14896 if (val === this.promise) {
14897 this.$$reject($qMinErr(
14899 "Expected promise to be resolved with value other than itself '{0}'",
14902 this.$$resolve(val);
14907 $$resolve: function(val) {
14910 fns = callOnce(this, this.$$resolve, this.$$reject);
14912 if ((isObject(val) || isFunction(val))) then = val && val.then;
14913 if (isFunction(then)) {
14914 this.promise.$$state.status = -1;
14915 then.call(val, fns[0], fns[1], this.notify);
14917 this.promise.$$state.value = val;
14918 this.promise.$$state.status = 1;
14919 scheduleProcessQueue(this.promise.$$state);
14923 exceptionHandler(e);
14927 reject: function(reason) {
14928 if (this.promise.$$state.status) return;
14929 this.$$reject(reason);
14932 $$reject: function(reason) {
14933 this.promise.$$state.value = reason;
14934 this.promise.$$state.status = 2;
14935 scheduleProcessQueue(this.promise.$$state);
14938 notify: function(progress) {
14939 var callbacks = this.promise.$$state.pending;
14941 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14942 nextTick(function() {
14943 var callback, result;
14944 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14945 result = callbacks[i][0];
14946 callback = callbacks[i][3];
14948 result.notify(isFunction(callback) ? callback(progress) : progress);
14950 exceptionHandler(e);
14964 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14965 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14966 * a promise chain, you don't need to worry about it.
14968 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14969 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14970 * a promise error callback and you want to forward the error to the promise derived from the
14971 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14975 * promiseB = promiseA.then(function(result) {
14976 * // success: do something and resolve promiseB
14977 * // with the old or a new result
14979 * }, function(reason) {
14980 * // error: handle the error if possible and
14981 * // resolve promiseB with newPromiseOrValue,
14982 * // otherwise forward the rejection to promiseB
14983 * if (canHandle(reason)) {
14984 * // handle the error and recover
14985 * return newPromiseOrValue;
14987 * return $q.reject(reason);
14991 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14992 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14994 var reject = function(reason) {
14995 var result = new Deferred();
14996 result.reject(reason);
14997 return result.promise;
15000 var makePromise = function makePromise(value, resolved) {
15001 var result = new Deferred();
15003 result.resolve(value);
15005 result.reject(value);
15007 return result.promise;
15010 var handleCallback = function handleCallback(value, isResolved, callback) {
15011 var callbackOutput = null;
15013 if (isFunction(callback)) callbackOutput = callback();
15015 return makePromise(e, false);
15017 if (isPromiseLike(callbackOutput)) {
15018 return callbackOutput.then(function() {
15019 return makePromise(value, isResolved);
15020 }, function(error) {
15021 return makePromise(error, false);
15024 return makePromise(value, isResolved);
15034 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
15035 * This is useful when you are dealing with an object that might or might not be a promise, or if
15036 * the promise comes from a source that can't be trusted.
15038 * @param {*} value Value or a promise
15039 * @param {Function=} successCallback
15040 * @param {Function=} errorCallback
15041 * @param {Function=} progressCallback
15042 * @returns {Promise} Returns a promise of the passed value or promise
15046 var when = function(value, callback, errback, progressBack) {
15047 var result = new Deferred();
15048 result.resolve(value);
15049 return result.promise.then(callback, errback, progressBack);
15058 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
15060 * @param {*} value Value or a promise
15061 * @param {Function=} successCallback
15062 * @param {Function=} errorCallback
15063 * @param {Function=} progressCallback
15064 * @returns {Promise} Returns a promise of the passed value or promise
15066 var resolve = when;
15074 * Combines multiple promises into a single promise that is resolved when all of the input
15075 * promises are resolved.
15077 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15078 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15079 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15080 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15081 * with the same rejection value.
15084 function all(promises) {
15085 var deferred = new Deferred(),
15087 results = isArray(promises) ? [] : {};
15089 forEach(promises, function(promise, key) {
15091 when(promise).then(function(value) {
15092 if (results.hasOwnProperty(key)) return;
15093 results[key] = value;
15094 if (!(--counter)) deferred.resolve(results);
15095 }, function(reason) {
15096 if (results.hasOwnProperty(key)) return;
15097 deferred.reject(reason);
15101 if (counter === 0) {
15102 deferred.resolve(results);
15105 return deferred.promise;
15108 var $Q = function Q(resolver) {
15109 if (!isFunction(resolver)) {
15110 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15113 if (!(this instanceof Q)) {
15114 // More useful when $Q is the Promise itself.
15115 return new Q(resolver);
15118 var deferred = new Deferred();
15120 function resolveFn(value) {
15121 deferred.resolve(value);
15124 function rejectFn(reason) {
15125 deferred.reject(reason);
15128 resolver(resolveFn, rejectFn);
15130 return deferred.promise;
15134 $Q.reject = reject;
15136 $Q.resolve = resolve;
15142 function $$RAFProvider() { //rAF
15143 this.$get = ['$window', '$timeout', function($window, $timeout) {
15144 var requestAnimationFrame = $window.requestAnimationFrame ||
15145 $window.webkitRequestAnimationFrame;
15147 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15148 $window.webkitCancelAnimationFrame ||
15149 $window.webkitCancelRequestAnimationFrame;
15151 var rafSupported = !!requestAnimationFrame;
15152 var raf = rafSupported
15154 var id = requestAnimationFrame(fn);
15155 return function() {
15156 cancelAnimationFrame(id);
15160 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15161 return function() {
15162 $timeout.cancel(timer);
15166 raf.supported = rafSupported;
15175 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15177 * The typical use of scope is to watch the expressions, which most of the time return the same
15178 * value as last time so we optimize the operation.
15180 * Closures construction is expensive in terms of speed as well as memory:
15181 * - No closures, instead use prototypical inheritance for API
15182 * - Internal state needs to be stored on scope directly, which means that private state is
15183 * exposed as $$____ properties
15185 * Loop operations are optimized by using while(count--) { ... }
15186 * - This means that in order to keep the same order of execution as addition we have to add
15187 * items to the array at the beginning (unshift) instead of at the end (push)
15189 * Child scopes are created and removed often
15190 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15192 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15193 * in the same way as watch. Watch requires return of the initialization function which is expensive
15200 * @name $rootScopeProvider
15203 * Provider for the $rootScope service.
15208 * @name $rootScopeProvider#digestTtl
15211 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15212 * assuming that the model is unstable.
15214 * The current default is 10 iterations.
15216 * In complex applications it's possible that the dependencies between `$watch`s will result in
15217 * several digest iterations. However if an application needs more than the default 10 digest
15218 * iterations for its model to stabilize then you should investigate what is causing the model to
15219 * continuously change during the digest.
15221 * Increasing the TTL could have performance implications, so you should not change it without
15222 * proper justification.
15224 * @param {number} limit The number of digest iterations.
15233 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15234 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15235 * between the model and the view, via a mechanism for watching the model for changes.
15236 * They also provide event emission/broadcast and subscription facility. See the
15237 * {@link guide/scope developer guide on scopes}.
15239 function $RootScopeProvider() {
15241 var $rootScopeMinErr = minErr('$rootScope');
15242 var lastDirtyWatch = null;
15243 var applyAsyncId = null;
15245 this.digestTtl = function(value) {
15246 if (arguments.length) {
15252 function createChildScopeClass(parent) {
15253 function ChildScope() {
15254 this.$$watchers = this.$$nextSibling =
15255 this.$$childHead = this.$$childTail = null;
15256 this.$$listeners = {};
15257 this.$$listenerCount = {};
15258 this.$$watchersCount = 0;
15259 this.$id = nextUid();
15260 this.$$ChildScope = null;
15262 ChildScope.prototype = parent;
15266 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15267 function($injector, $exceptionHandler, $parse, $browser) {
15269 function destroyChildScope($event) {
15270 $event.currentScope.$$destroyed = true;
15273 function cleanUpScope($scope) {
15276 // There is a memory leak in IE9 if all child scopes are not disconnected
15277 // completely when a scope is destroyed. So this code will recurse up through
15278 // all this scopes children
15280 // See issue https://github.com/angular/angular.js/issues/10706
15281 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15282 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15285 // The code below works around IE9 and V8's memory leaks
15288 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15289 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15290 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15292 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15293 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15298 * @name $rootScope.Scope
15301 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15302 * {@link auto.$injector $injector}. Child scopes are created using the
15303 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15304 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15305 * an in-depth introduction and usage examples.
15309 * A scope can inherit from a parent scope, as in this example:
15311 var parent = $rootScope;
15312 var child = parent.$new();
15314 parent.salutation = "Hello";
15315 expect(child.salutation).toEqual('Hello');
15317 child.salutation = "Welcome";
15318 expect(child.salutation).toEqual('Welcome');
15319 expect(parent.salutation).toEqual('Hello');
15322 * When interacting with `Scope` in tests, additional helper methods are available on the
15323 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15327 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15328 * provided for the current scope. Defaults to {@link ng}.
15329 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15330 * append/override services provided by `providers`. This is handy
15331 * when unit-testing and having the need to override a default
15333 * @returns {Object} Newly created scope.
15337 this.$id = nextUid();
15338 this.$$phase = this.$parent = this.$$watchers =
15339 this.$$nextSibling = this.$$prevSibling =
15340 this.$$childHead = this.$$childTail = null;
15342 this.$$destroyed = false;
15343 this.$$listeners = {};
15344 this.$$listenerCount = {};
15345 this.$$watchersCount = 0;
15346 this.$$isolateBindings = null;
15351 * @name $rootScope.Scope#$id
15354 * Unique scope ID (monotonically increasing) useful for debugging.
15359 * @name $rootScope.Scope#$parent
15362 * Reference to the parent scope.
15367 * @name $rootScope.Scope#$root
15370 * Reference to the root scope.
15373 Scope.prototype = {
15374 constructor: Scope,
15377 * @name $rootScope.Scope#$new
15381 * Creates a new child {@link ng.$rootScope.Scope scope}.
15383 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15384 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15386 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15387 * desired for the scope and its child scopes to be permanently detached from the parent and
15388 * thus stop participating in model change detection and listener notification by invoking.
15390 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15391 * parent scope. The scope is isolated, as it can not see parent scope properties.
15392 * When creating widgets, it is useful for the widget to not accidentally read parent
15395 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15396 * of the newly created scope. Defaults to `this` scope if not provided.
15397 * This is used when creating a transclude scope to correctly place it
15398 * in the scope hierarchy while maintaining the correct prototypical
15401 * @returns {Object} The newly created child scope.
15404 $new: function(isolate, parent) {
15407 parent = parent || this;
15410 child = new Scope();
15411 child.$root = this.$root;
15413 // Only create a child scope class if somebody asks for one,
15414 // but cache it to allow the VM to optimize lookups.
15415 if (!this.$$ChildScope) {
15416 this.$$ChildScope = createChildScopeClass(this);
15418 child = new this.$$ChildScope();
15420 child.$parent = parent;
15421 child.$$prevSibling = parent.$$childTail;
15422 if (parent.$$childHead) {
15423 parent.$$childTail.$$nextSibling = child;
15424 parent.$$childTail = child;
15426 parent.$$childHead = parent.$$childTail = child;
15429 // When the new scope is not isolated or we inherit from `this`, and
15430 // the parent scope is destroyed, the property `$$destroyed` is inherited
15431 // prototypically. In all other cases, this property needs to be set
15432 // when the parent scope is destroyed.
15433 // The listener needs to be added after the parent is set
15434 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15441 * @name $rootScope.Scope#$watch
15445 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15447 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15448 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15449 * its value when executed multiple times with the same input because it may be executed multiple
15450 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15451 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15452 * - The `listener` is called only when the value from the current `watchExpression` and the
15453 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15454 * see below). Inequality is determined according to reference inequality,
15455 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15456 * via the `!==` Javascript operator, unless `objectEquality == true`
15458 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15459 * according to the {@link angular.equals} function. To save the value of the object for
15460 * later comparison, the {@link angular.copy} function is used. This therefore means that
15461 * watching complex objects will have adverse memory and performance implications.
15462 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15463 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15464 * iteration limit is 10 to prevent an infinite loop deadlock.
15467 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15468 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15469 * multiple calls to your `watchExpression` because it will execute multiple times in a
15470 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15472 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15473 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15474 * watcher. In rare cases, this is undesirable because the listener is called when the result
15475 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15476 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15477 * listener was called due to initialization.
15483 // let's assume that scope was dependency injected as the $rootScope
15484 var scope = $rootScope;
15485 scope.name = 'misko';
15488 expect(scope.counter).toEqual(0);
15489 scope.$watch('name', function(newValue, oldValue) {
15490 scope.counter = scope.counter + 1;
15492 expect(scope.counter).toEqual(0);
15495 // the listener is always called during the first $digest loop after it was registered
15496 expect(scope.counter).toEqual(1);
15499 // but now it will not be called unless the value changes
15500 expect(scope.counter).toEqual(1);
15502 scope.name = 'adam';
15504 expect(scope.counter).toEqual(2);
15508 // Using a function as a watchExpression
15510 scope.foodCounter = 0;
15511 expect(scope.foodCounter).toEqual(0);
15513 // This function returns the value being watched. It is called for each turn of the $digest loop
15514 function() { return food; },
15515 // This is the change listener, called when the value returned from the above function changes
15516 function(newValue, oldValue) {
15517 if ( newValue !== oldValue ) {
15518 // Only increment the counter if the value changed
15519 scope.foodCounter = scope.foodCounter + 1;
15523 // No digest has been run so the counter will be zero
15524 expect(scope.foodCounter).toEqual(0);
15526 // Run the digest but since food has not changed count will still be zero
15528 expect(scope.foodCounter).toEqual(0);
15530 // Update food and run digest. Now the counter will increment
15531 food = 'cheeseburger';
15533 expect(scope.foodCounter).toEqual(1);
15539 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15540 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15541 * a call to the `listener`.
15543 * - `string`: Evaluated as {@link guide/expression expression}
15544 * - `function(scope)`: called with current `scope` as a parameter.
15545 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15546 * of `watchExpression` changes.
15548 * - `newVal` contains the current value of the `watchExpression`
15549 * - `oldVal` contains the previous value of the `watchExpression`
15550 * - `scope` refers to the current scope
15551 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15552 * comparing for reference equality.
15553 * @returns {function()} Returns a deregistration function for this listener.
15555 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15556 var get = $parse(watchExp);
15558 if (get.$$watchDelegate) {
15559 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15562 array = scope.$$watchers,
15565 last: initWatchVal,
15567 exp: prettyPrintExpression || watchExp,
15568 eq: !!objectEquality
15571 lastDirtyWatch = null;
15573 if (!isFunction(listener)) {
15578 array = scope.$$watchers = [];
15580 // we use unshift since we use a while loop in $digest for speed.
15581 // the while loop reads in reverse order.
15582 array.unshift(watcher);
15583 incrementWatchersCount(this, 1);
15585 return function deregisterWatch() {
15586 if (arrayRemove(array, watcher) >= 0) {
15587 incrementWatchersCount(scope, -1);
15589 lastDirtyWatch = null;
15595 * @name $rootScope.Scope#$watchGroup
15599 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15600 * If any one expression in the collection changes the `listener` is executed.
15602 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15603 * call to $digest() to see if any items changes.
15604 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15606 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15607 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15609 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15610 * expression in `watchExpressions` changes
15611 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15612 * those of `watchExpression`
15613 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15614 * those of `watchExpression`
15615 * The `scope` refers to the current scope.
15616 * @returns {function()} Returns a de-registration function for all listeners.
15618 $watchGroup: function(watchExpressions, listener) {
15619 var oldValues = new Array(watchExpressions.length);
15620 var newValues = new Array(watchExpressions.length);
15621 var deregisterFns = [];
15623 var changeReactionScheduled = false;
15624 var firstRun = true;
15626 if (!watchExpressions.length) {
15627 // No expressions means we call the listener ASAP
15628 var shouldCall = true;
15629 self.$evalAsync(function() {
15630 if (shouldCall) listener(newValues, newValues, self);
15632 return function deregisterWatchGroup() {
15633 shouldCall = false;
15637 if (watchExpressions.length === 1) {
15638 // Special case size of one
15639 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15640 newValues[0] = value;
15641 oldValues[0] = oldValue;
15642 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15646 forEach(watchExpressions, function(expr, i) {
15647 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15648 newValues[i] = value;
15649 oldValues[i] = oldValue;
15650 if (!changeReactionScheduled) {
15651 changeReactionScheduled = true;
15652 self.$evalAsync(watchGroupAction);
15655 deregisterFns.push(unwatchFn);
15658 function watchGroupAction() {
15659 changeReactionScheduled = false;
15663 listener(newValues, newValues, self);
15665 listener(newValues, oldValues, self);
15669 return function deregisterWatchGroup() {
15670 while (deregisterFns.length) {
15671 deregisterFns.shift()();
15679 * @name $rootScope.Scope#$watchCollection
15683 * Shallow watches the properties of an object and fires whenever any of the properties change
15684 * (for arrays, this implies watching the array items; for object maps, this implies watching
15685 * the properties). If a change is detected, the `listener` callback is fired.
15687 * - The `obj` collection is observed via standard $watch operation and is examined on every
15688 * call to $digest() to see if any items have been added, removed, or moved.
15689 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15690 * adding, removing, and moving items belonging to an object or array.
15695 $scope.names = ['igor', 'matias', 'misko', 'james'];
15696 $scope.dataCount = 4;
15698 $scope.$watchCollection('names', function(newNames, oldNames) {
15699 $scope.dataCount = newNames.length;
15702 expect($scope.dataCount).toEqual(4);
15705 //still at 4 ... no changes
15706 expect($scope.dataCount).toEqual(4);
15708 $scope.names.pop();
15711 //now there's been a change
15712 expect($scope.dataCount).toEqual(3);
15716 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15717 * expression value should evaluate to an object or an array which is observed on each
15718 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15719 * collection will trigger a call to the `listener`.
15721 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15722 * when a change is detected.
15723 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15724 * - The `oldCollection` object is a copy of the former collection data.
15725 * Due to performance considerations, the`oldCollection` value is computed only if the
15726 * `listener` function declares two or more arguments.
15727 * - The `scope` argument refers to the current scope.
15729 * @returns {function()} Returns a de-registration function for this listener. When the
15730 * de-registration function is executed, the internal watch operation is terminated.
15732 $watchCollection: function(obj, listener) {
15733 $watchCollectionInterceptor.$stateful = true;
15736 // the current value, updated on each dirty-check run
15738 // a shallow copy of the newValue from the last dirty-check run,
15739 // updated to match newValue during dirty-check run
15741 // a shallow copy of the newValue from when the last change happened
15743 // only track veryOldValue if the listener is asking for it
15744 var trackVeryOldValue = (listener.length > 1);
15745 var changeDetected = 0;
15746 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15747 var internalArray = [];
15748 var internalObject = {};
15749 var initRun = true;
15752 function $watchCollectionInterceptor(_value) {
15754 var newLength, key, bothNaN, newItem, oldItem;
15756 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15757 if (isUndefined(newValue)) return;
15759 if (!isObject(newValue)) { // if primitive
15760 if (oldValue !== newValue) {
15761 oldValue = newValue;
15764 } else if (isArrayLike(newValue)) {
15765 if (oldValue !== internalArray) {
15766 // we are transitioning from something which was not an array into array.
15767 oldValue = internalArray;
15768 oldLength = oldValue.length = 0;
15772 newLength = newValue.length;
15774 if (oldLength !== newLength) {
15775 // if lengths do not match we need to trigger change notification
15777 oldValue.length = oldLength = newLength;
15779 // copy the items to oldValue and look for changes.
15780 for (var i = 0; i < newLength; i++) {
15781 oldItem = oldValue[i];
15782 newItem = newValue[i];
15784 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15785 if (!bothNaN && (oldItem !== newItem)) {
15787 oldValue[i] = newItem;
15791 if (oldValue !== internalObject) {
15792 // we are transitioning from something which was not an object into object.
15793 oldValue = internalObject = {};
15797 // copy the items to oldValue and look for changes.
15799 for (key in newValue) {
15800 if (hasOwnProperty.call(newValue, key)) {
15802 newItem = newValue[key];
15803 oldItem = oldValue[key];
15805 if (key in oldValue) {
15806 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15807 if (!bothNaN && (oldItem !== newItem)) {
15809 oldValue[key] = newItem;
15813 oldValue[key] = newItem;
15818 if (oldLength > newLength) {
15819 // we used to have more keys, need to find them and destroy them.
15821 for (key in oldValue) {
15822 if (!hasOwnProperty.call(newValue, key)) {
15824 delete oldValue[key];
15829 return changeDetected;
15832 function $watchCollectionAction() {
15835 listener(newValue, newValue, self);
15837 listener(newValue, veryOldValue, self);
15840 // make a copy for the next time a collection is changed
15841 if (trackVeryOldValue) {
15842 if (!isObject(newValue)) {
15844 veryOldValue = newValue;
15845 } else if (isArrayLike(newValue)) {
15846 veryOldValue = new Array(newValue.length);
15847 for (var i = 0; i < newValue.length; i++) {
15848 veryOldValue[i] = newValue[i];
15850 } else { // if object
15852 for (var key in newValue) {
15853 if (hasOwnProperty.call(newValue, key)) {
15854 veryOldValue[key] = newValue[key];
15861 return this.$watch(changeDetector, $watchCollectionAction);
15866 * @name $rootScope.Scope#$digest
15870 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15871 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15872 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15873 * until no more listeners are firing. This means that it is possible to get into an infinite
15874 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15875 * iterations exceeds 10.
15877 * Usually, you don't call `$digest()` directly in
15878 * {@link ng.directive:ngController controllers} or in
15879 * {@link ng.$compileProvider#directive directives}.
15880 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15881 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15883 * If you want to be notified whenever `$digest()` is called,
15884 * you can register a `watchExpression` function with
15885 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15887 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15892 scope.name = 'misko';
15895 expect(scope.counter).toEqual(0);
15896 scope.$watch('name', function(newValue, oldValue) {
15897 scope.counter = scope.counter + 1;
15899 expect(scope.counter).toEqual(0);
15902 // the listener is always called during the first $digest loop after it was registered
15903 expect(scope.counter).toEqual(1);
15906 // but now it will not be called unless the value changes
15907 expect(scope.counter).toEqual(1);
15909 scope.name = 'adam';
15911 expect(scope.counter).toEqual(2);
15915 $digest: function() {
15916 var watch, value, last,
15920 next, current, target = this,
15922 logIdx, logMsg, asyncTask;
15924 beginPhase('$digest');
15925 // Check for changes to browser url that happened in sync before the call to $digest
15926 $browser.$$checkUrlChange();
15928 if (this === $rootScope && applyAsyncId !== null) {
15929 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15930 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15931 $browser.defer.cancel(applyAsyncId);
15935 lastDirtyWatch = null;
15937 do { // "while dirty" loop
15941 while (asyncQueue.length) {
15943 asyncTask = asyncQueue.shift();
15944 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15946 $exceptionHandler(e);
15948 lastDirtyWatch = null;
15951 traverseScopesLoop:
15952 do { // "traverse the scopes" loop
15953 if ((watchers = current.$$watchers)) {
15954 // process our watches
15955 length = watchers.length;
15958 watch = watchers[length];
15959 // Most common watches are on primitives, in which case we can short
15960 // circuit it with === operator, only when === fails do we use .equals
15962 if ((value = watch.get(current)) !== (last = watch.last) &&
15964 ? equals(value, last)
15965 : (typeof value === 'number' && typeof last === 'number'
15966 && isNaN(value) && isNaN(last)))) {
15968 lastDirtyWatch = watch;
15969 watch.last = watch.eq ? copy(value, null) : value;
15970 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15973 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15974 watchLog[logIdx].push({
15975 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15980 } else if (watch === lastDirtyWatch) {
15981 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15982 // have already been tested.
15984 break traverseScopesLoop;
15988 $exceptionHandler(e);
15993 // Insanity Warning: scope depth-first traversal
15994 // yes, this code is a bit crazy, but it works and we have tests to prove it!
15995 // this piece should be kept in sync with the traversal in $broadcast
15996 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
15997 (current !== target && current.$$nextSibling)))) {
15998 while (current !== target && !(next = current.$$nextSibling)) {
15999 current = current.$parent;
16002 } while ((current = next));
16004 // `break traverseScopesLoop;` takes us to here
16006 if ((dirty || asyncQueue.length) && !(ttl--)) {
16008 throw $rootScopeMinErr('infdig',
16009 '{0} $digest() iterations reached. Aborting!\n' +
16010 'Watchers fired in the last 5 iterations: {1}',
16014 } while (dirty || asyncQueue.length);
16018 while (postDigestQueue.length) {
16020 postDigestQueue.shift()();
16022 $exceptionHandler(e);
16030 * @name $rootScope.Scope#$destroy
16031 * @eventType broadcast on scope being destroyed
16034 * Broadcasted when a scope and its children are being destroyed.
16036 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16037 * clean up DOM bindings before an element is removed from the DOM.
16042 * @name $rootScope.Scope#$destroy
16046 * Removes the current scope (and all of its children) from the parent scope. Removal implies
16047 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
16048 * propagate to the current scope and its children. Removal also implies that the current
16049 * scope is eligible for garbage collection.
16051 * The `$destroy()` is usually used by directives such as
16052 * {@link ng.directive:ngRepeat ngRepeat} for managing the
16053 * unrolling of the loop.
16055 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
16056 * Application code can register a `$destroy` event handler that will give it a chance to
16057 * perform any necessary cleanup.
16059 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16060 * clean up DOM bindings before an element is removed from the DOM.
16062 $destroy: function() {
16063 // We can't destroy a scope that has been already destroyed.
16064 if (this.$$destroyed) return;
16065 var parent = this.$parent;
16067 this.$broadcast('$destroy');
16068 this.$$destroyed = true;
16070 if (this === $rootScope) {
16071 //Remove handlers attached to window when $rootScope is removed
16072 $browser.$$applicationDestroyed();
16075 incrementWatchersCount(this, -this.$$watchersCount);
16076 for (var eventName in this.$$listenerCount) {
16077 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16080 // sever all the references to parent scopes (after this cleanup, the current scope should
16081 // not be retained by any of our references and should be eligible for garbage collection)
16082 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16083 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16084 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16085 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16087 // Disable listeners, watchers and apply/digest methods
16088 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16089 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16090 this.$$listeners = {};
16092 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16093 this.$$nextSibling = null;
16094 cleanUpScope(this);
16099 * @name $rootScope.Scope#$eval
16103 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16104 * the expression are propagated (uncaught). This is useful when evaluating Angular
16109 var scope = ng.$rootScope.Scope();
16113 expect(scope.$eval('a+b')).toEqual(3);
16114 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16117 * @param {(string|function())=} expression An angular expression to be executed.
16119 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16120 * - `function(scope)`: execute the function with the current `scope` parameter.
16122 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16123 * @returns {*} The result of evaluating the expression.
16125 $eval: function(expr, locals) {
16126 return $parse(expr)(this, locals);
16131 * @name $rootScope.Scope#$evalAsync
16135 * Executes the expression on the current scope at a later point in time.
16137 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16140 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16142 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16143 * `expression` execution.
16145 * Any exceptions from the execution of the expression are forwarded to the
16146 * {@link ng.$exceptionHandler $exceptionHandler} service.
16148 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16149 * will be scheduled. However, it is encouraged to always call code that changes the model
16150 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16152 * @param {(string|function())=} expression An angular expression to be executed.
16154 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16155 * - `function(scope)`: execute the function with the current `scope` parameter.
16157 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16159 $evalAsync: function(expr, locals) {
16160 // if we are outside of an $digest loop and this is the first time we are scheduling async
16161 // task also schedule async auto-flush
16162 if (!$rootScope.$$phase && !asyncQueue.length) {
16163 $browser.defer(function() {
16164 if (asyncQueue.length) {
16165 $rootScope.$digest();
16170 asyncQueue.push({scope: this, expression: expr, locals: locals});
16173 $$postDigest: function(fn) {
16174 postDigestQueue.push(fn);
16179 * @name $rootScope.Scope#$apply
16183 * `$apply()` is used to execute an expression in angular from outside of the angular
16184 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16185 * Because we are calling into the angular framework we need to perform proper scope life
16186 * cycle of {@link ng.$exceptionHandler exception handling},
16187 * {@link ng.$rootScope.Scope#$digest executing watches}.
16191 * # Pseudo-Code of `$apply()`
16193 function $apply(expr) {
16195 return $eval(expr);
16197 $exceptionHandler(e);
16205 * Scope's `$apply()` method transitions through the following stages:
16207 * 1. The {@link guide/expression expression} is executed using the
16208 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16209 * 2. Any exceptions from the execution of the expression are forwarded to the
16210 * {@link ng.$exceptionHandler $exceptionHandler} service.
16211 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16212 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16215 * @param {(string|function())=} exp An angular expression to be executed.
16217 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16218 * - `function(scope)`: execute the function with current `scope` parameter.
16220 * @returns {*} The result of evaluating the expression.
16222 $apply: function(expr) {
16224 beginPhase('$apply');
16226 return this.$eval(expr);
16231 $exceptionHandler(e);
16234 $rootScope.$digest();
16236 $exceptionHandler(e);
16244 * @name $rootScope.Scope#$applyAsync
16248 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16249 * varies across browsers, but is typically around ~10 milliseconds.
16251 * This can be used to queue up multiple expressions which need to be evaluated in the same
16254 * @param {(string|function())=} exp An angular expression to be executed.
16256 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16257 * - `function(scope)`: execute the function with current `scope` parameter.
16259 $applyAsync: function(expr) {
16261 expr && applyAsyncQueue.push($applyAsyncExpression);
16262 scheduleApplyAsync();
16264 function $applyAsyncExpression() {
16271 * @name $rootScope.Scope#$on
16275 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16276 * discussion of event life cycle.
16278 * The event listener function format is: `function(event, args...)`. The `event` object
16279 * passed into the listener has the following attributes:
16281 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16283 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16284 * event propagates through the scope hierarchy, this property is set to null.
16285 * - `name` - `{string}`: name of the event.
16286 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16287 * further event propagation (available only for events that were `$emit`-ed).
16288 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16290 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16292 * @param {string} name Event name to listen on.
16293 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16294 * @returns {function()} Returns a deregistration function for this listener.
16296 $on: function(name, listener) {
16297 var namedListeners = this.$$listeners[name];
16298 if (!namedListeners) {
16299 this.$$listeners[name] = namedListeners = [];
16301 namedListeners.push(listener);
16303 var current = this;
16305 if (!current.$$listenerCount[name]) {
16306 current.$$listenerCount[name] = 0;
16308 current.$$listenerCount[name]++;
16309 } while ((current = current.$parent));
16312 return function() {
16313 var indexOfListener = namedListeners.indexOf(listener);
16314 if (indexOfListener !== -1) {
16315 namedListeners[indexOfListener] = null;
16316 decrementListenerCount(self, 1, name);
16324 * @name $rootScope.Scope#$emit
16328 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16329 * registered {@link ng.$rootScope.Scope#$on} listeners.
16331 * The event life cycle starts at the scope on which `$emit` was called. All
16332 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16333 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16334 * registered listeners along the way. The event will stop propagating if one of the listeners
16337 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16338 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16340 * @param {string} name Event name to emit.
16341 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16342 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16344 $emit: function(name, args) {
16348 stopPropagation = false,
16351 targetScope: scope,
16352 stopPropagation: function() {stopPropagation = true;},
16353 preventDefault: function() {
16354 event.defaultPrevented = true;
16356 defaultPrevented: false
16358 listenerArgs = concat([event], arguments, 1),
16362 namedListeners = scope.$$listeners[name] || empty;
16363 event.currentScope = scope;
16364 for (i = 0, length = namedListeners.length; i < length; i++) {
16366 // if listeners were deregistered, defragment the array
16367 if (!namedListeners[i]) {
16368 namedListeners.splice(i, 1);
16374 //allow all listeners attached to the current scope to run
16375 namedListeners[i].apply(null, listenerArgs);
16377 $exceptionHandler(e);
16380 //if any listener on the current scope stops propagation, prevent bubbling
16381 if (stopPropagation) {
16382 event.currentScope = null;
16386 scope = scope.$parent;
16389 event.currentScope = null;
16397 * @name $rootScope.Scope#$broadcast
16401 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16402 * registered {@link ng.$rootScope.Scope#$on} listeners.
16404 * The event life cycle starts at the scope on which `$broadcast` was called. All
16405 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16406 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16407 * scope and calls all registered listeners along the way. The event cannot be canceled.
16409 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16410 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16412 * @param {string} name Event name to broadcast.
16413 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16414 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16416 $broadcast: function(name, args) {
16422 targetScope: target,
16423 preventDefault: function() {
16424 event.defaultPrevented = true;
16426 defaultPrevented: false
16429 if (!target.$$listenerCount[name]) return event;
16431 var listenerArgs = concat([event], arguments, 1),
16432 listeners, i, length;
16434 //down while you can, then up and next sibling or up and next sibling until back at root
16435 while ((current = next)) {
16436 event.currentScope = current;
16437 listeners = current.$$listeners[name] || [];
16438 for (i = 0, length = listeners.length; i < length; i++) {
16439 // if listeners were deregistered, defragment the array
16440 if (!listeners[i]) {
16441 listeners.splice(i, 1);
16448 listeners[i].apply(null, listenerArgs);
16450 $exceptionHandler(e);
16454 // Insanity Warning: scope depth-first traversal
16455 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16456 // this piece should be kept in sync with the traversal in $digest
16457 // (though it differs due to having the extra check for $$listenerCount)
16458 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16459 (current !== target && current.$$nextSibling)))) {
16460 while (current !== target && !(next = current.$$nextSibling)) {
16461 current = current.$parent;
16466 event.currentScope = null;
16471 var $rootScope = new Scope();
16473 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16474 var asyncQueue = $rootScope.$$asyncQueue = [];
16475 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16476 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16481 function beginPhase(phase) {
16482 if ($rootScope.$$phase) {
16483 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16486 $rootScope.$$phase = phase;
16489 function clearPhase() {
16490 $rootScope.$$phase = null;
16493 function incrementWatchersCount(current, count) {
16495 current.$$watchersCount += count;
16496 } while ((current = current.$parent));
16499 function decrementListenerCount(current, count, name) {
16501 current.$$listenerCount[name] -= count;
16503 if (current.$$listenerCount[name] === 0) {
16504 delete current.$$listenerCount[name];
16506 } while ((current = current.$parent));
16510 * function used as an initial value for watchers.
16511 * because it's unique we can easily tell it apart from other values
16513 function initWatchVal() {}
16515 function flushApplyAsync() {
16516 while (applyAsyncQueue.length) {
16518 applyAsyncQueue.shift()();
16520 $exceptionHandler(e);
16523 applyAsyncId = null;
16526 function scheduleApplyAsync() {
16527 if (applyAsyncId === null) {
16528 applyAsyncId = $browser.defer(function() {
16529 $rootScope.$apply(flushApplyAsync);
16538 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16540 function $$SanitizeUriProvider() {
16541 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16542 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16546 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16547 * urls during a[href] sanitization.
16549 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16551 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16552 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16553 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16554 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16556 * @param {RegExp=} regexp New regexp to whitelist urls with.
16557 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16558 * chaining otherwise.
16560 this.aHrefSanitizationWhitelist = function(regexp) {
16561 if (isDefined(regexp)) {
16562 aHrefSanitizationWhitelist = regexp;
16565 return aHrefSanitizationWhitelist;
16571 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16572 * urls during img[src] sanitization.
16574 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16576 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16577 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16578 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16579 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16581 * @param {RegExp=} regexp New regexp to whitelist urls with.
16582 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16583 * chaining otherwise.
16585 this.imgSrcSanitizationWhitelist = function(regexp) {
16586 if (isDefined(regexp)) {
16587 imgSrcSanitizationWhitelist = regexp;
16590 return imgSrcSanitizationWhitelist;
16593 this.$get = function() {
16594 return function sanitizeUri(uri, isImage) {
16595 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16597 normalizedVal = urlResolve(uri).href;
16598 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16599 return 'unsafe:' + normalizedVal;
16606 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16607 * Any commits to this file should be reviewed with security in mind. *
16608 * Changes to this file can potentially create security vulnerabilities. *
16609 * An approval from 2 Core members with history of modifying *
16610 * this file is required. *
16612 * Does the change somehow allow for arbitrary javascript to be executed? *
16613 * Or allows for someone to change the prototype of built-in objects? *
16614 * Or gives undesired access to variables likes document or window? *
16615 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16617 var $sceMinErr = minErr('$sce');
16619 var SCE_CONTEXTS = {
16623 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16624 // url. (e.g. ng-include, script src, templateUrl)
16625 RESOURCE_URL: 'resourceUrl',
16629 // Helper functions follow.
16631 function adjustMatcher(matcher) {
16632 if (matcher === 'self') {
16634 } else if (isString(matcher)) {
16635 // Strings match exactly except for 2 wildcards - '*' and '**'.
16636 // '*' matches any character except those from the set ':/.?&'.
16637 // '**' matches any character (like .* in a RegExp).
16638 // More than 2 *'s raises an error as it's ill defined.
16639 if (matcher.indexOf('***') > -1) {
16640 throw $sceMinErr('iwcard',
16641 'Illegal sequence *** in string matcher. String: {0}', matcher);
16643 matcher = escapeForRegexp(matcher).
16644 replace('\\*\\*', '.*').
16645 replace('\\*', '[^:/.?&;]*');
16646 return new RegExp('^' + matcher + '$');
16647 } else if (isRegExp(matcher)) {
16648 // The only other type of matcher allowed is a Regexp.
16649 // Match entire URL / disallow partial matches.
16650 // Flags are reset (i.e. no global, ignoreCase or multiline)
16651 return new RegExp('^' + matcher.source + '$');
16653 throw $sceMinErr('imatcher',
16654 'Matchers may only be "self", string patterns or RegExp objects');
16659 function adjustMatchers(matchers) {
16660 var adjustedMatchers = [];
16661 if (isDefined(matchers)) {
16662 forEach(matchers, function(matcher) {
16663 adjustedMatchers.push(adjustMatcher(matcher));
16666 return adjustedMatchers;
16672 * @name $sceDelegate
16677 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16678 * Contextual Escaping (SCE)} services to AngularJS.
16680 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16681 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16682 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16683 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16684 * work because `$sce` delegates to `$sceDelegate` for these operations.
16686 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16688 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16689 * can override it completely to change the behavior of `$sce`, the common case would
16690 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16691 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16692 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16693 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16694 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16699 * @name $sceDelegateProvider
16702 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16703 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16704 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16705 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16706 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16708 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16709 * Strict Contextual Escaping (SCE)}.
16711 * **Example**: Consider the following case. <a name="example"></a>
16713 * - your app is hosted at url `http://myapp.example.com/`
16714 * - but some of your templates are hosted on other domains you control such as
16715 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
16716 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16718 * Here is what a secure configuration for this scenario might look like:
16721 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16722 * $sceDelegateProvider.resourceUrlWhitelist([
16723 * // Allow same origin resource loads.
16725 * // Allow loading from our assets domain. Notice the difference between * and **.
16726 * 'http://srv*.assets.example.com/**'
16729 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16730 * $sceDelegateProvider.resourceUrlBlacklist([
16731 * 'http://myapp.example.com/clickThru**'
16737 function $SceDelegateProvider() {
16738 this.SCE_CONTEXTS = SCE_CONTEXTS;
16740 // Resource URLs can also be trusted by policy.
16741 var resourceUrlWhitelist = ['self'],
16742 resourceUrlBlacklist = [];
16746 * @name $sceDelegateProvider#resourceUrlWhitelist
16749 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16750 * provided. This must be an array or null. A snapshot of this array is used so further
16751 * changes to the array are ignored.
16753 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16754 * allowed in this array.
16756 * Note: **an empty whitelist array will block all URLs**!
16758 * @return {Array} the currently set whitelist array.
16760 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16761 * same origin resource requests.
16764 * Sets/Gets the whitelist of trusted resource URLs.
16766 this.resourceUrlWhitelist = function(value) {
16767 if (arguments.length) {
16768 resourceUrlWhitelist = adjustMatchers(value);
16770 return resourceUrlWhitelist;
16775 * @name $sceDelegateProvider#resourceUrlBlacklist
16778 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16779 * provided. This must be an array or null. A snapshot of this array is used so further
16780 * changes to the array are ignored.
16782 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16783 * allowed in this array.
16785 * The typical usage for the blacklist is to **block
16786 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16787 * these would otherwise be trusted but actually return content from the redirected domain.
16789 * Finally, **the blacklist overrides the whitelist** and has the final say.
16791 * @return {Array} the currently set blacklist array.
16793 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16794 * is no blacklist.)
16797 * Sets/Gets the blacklist of trusted resource URLs.
16800 this.resourceUrlBlacklist = function(value) {
16801 if (arguments.length) {
16802 resourceUrlBlacklist = adjustMatchers(value);
16804 return resourceUrlBlacklist;
16807 this.$get = ['$injector', function($injector) {
16809 var htmlSanitizer = function htmlSanitizer(html) {
16810 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16813 if ($injector.has('$sanitize')) {
16814 htmlSanitizer = $injector.get('$sanitize');
16818 function matchUrl(matcher, parsedUrl) {
16819 if (matcher === 'self') {
16820 return urlIsSameOrigin(parsedUrl);
16822 // definitely a regex. See adjustMatchers()
16823 return !!matcher.exec(parsedUrl.href);
16827 function isResourceUrlAllowedByPolicy(url) {
16828 var parsedUrl = urlResolve(url.toString());
16829 var i, n, allowed = false;
16830 // Ensure that at least one item from the whitelist allows this url.
16831 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16832 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16838 // Ensure that no item from the blacklist blocked this url.
16839 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16840 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16849 function generateHolderType(Base) {
16850 var holderType = function TrustedValueHolderType(trustedValue) {
16851 this.$$unwrapTrustedValue = function() {
16852 return trustedValue;
16856 holderType.prototype = new Base();
16858 holderType.prototype.valueOf = function sceValueOf() {
16859 return this.$$unwrapTrustedValue();
16861 holderType.prototype.toString = function sceToString() {
16862 return this.$$unwrapTrustedValue().toString();
16867 var trustedValueHolderBase = generateHolderType(),
16870 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16871 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16872 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16873 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16874 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16878 * @name $sceDelegate#trustAs
16881 * Returns an object that is trusted by angular for use in specified strict
16882 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16883 * attribute interpolation, any dom event binding attribute interpolation
16884 * such as for onclick, etc.) that uses the provided value.
16885 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16887 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16888 * resourceUrl, html, js and css.
16889 * @param {*} value The value that that should be considered trusted/safe.
16890 * @returns {*} A value that can be used to stand in for the provided `value` in places
16891 * where Angular expects a $sce.trustAs() return value.
16893 function trustAs(type, trustedValue) {
16894 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16895 if (!Constructor) {
16896 throw $sceMinErr('icontext',
16897 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16898 type, trustedValue);
16900 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16901 return trustedValue;
16903 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16904 // mutable objects, we ensure here that the value passed in is actually a string.
16905 if (typeof trustedValue !== 'string') {
16906 throw $sceMinErr('itype',
16907 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16910 return new Constructor(trustedValue);
16915 * @name $sceDelegate#valueOf
16918 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16919 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16920 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16922 * If the passed parameter is not a value that had been returned by {@link
16923 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16925 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16926 * call or anything else.
16927 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16928 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16929 * `value` unchanged.
16931 function valueOf(maybeTrusted) {
16932 if (maybeTrusted instanceof trustedValueHolderBase) {
16933 return maybeTrusted.$$unwrapTrustedValue();
16935 return maybeTrusted;
16941 * @name $sceDelegate#getTrusted
16944 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16945 * returns the originally supplied value if the queried context type is a supertype of the
16946 * created type. If this condition isn't satisfied, throws an exception.
16948 * @param {string} type The kind of context in which this value is to be used.
16949 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16950 * `$sceDelegate.trustAs`} call.
16951 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16952 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16954 function getTrusted(type, maybeTrusted) {
16955 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16956 return maybeTrusted;
16958 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16959 if (constructor && maybeTrusted instanceof constructor) {
16960 return maybeTrusted.$$unwrapTrustedValue();
16962 // If we get here, then we may only take one of two actions.
16963 // 1. sanitize the value for the requested type, or
16964 // 2. throw an exception.
16965 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16966 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16967 return maybeTrusted;
16969 throw $sceMinErr('insecurl',
16970 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16971 maybeTrusted.toString());
16973 } else if (type === SCE_CONTEXTS.HTML) {
16974 return htmlSanitizer(maybeTrusted);
16976 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16979 return { trustAs: trustAs,
16980 getTrusted: getTrusted,
16981 valueOf: valueOf };
16988 * @name $sceProvider
16991 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16992 * - enable/disable Strict Contextual Escaping (SCE) in a module
16993 * - override the default implementation with a custom delegate
16995 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
16998 /* jshint maxlen: false*/
17007 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
17009 * # Strict Contextual Escaping
17011 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
17012 * contexts to result in a value that is marked as safe to use for that context. One example of
17013 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
17014 * to these contexts as privileged or SCE contexts.
17016 * As of version 1.2, Angular ships with SCE enabled by default.
17018 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
17019 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
17020 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17021 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
17022 * to the top of your HTML document.
17024 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
17025 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
17027 * Here's an example of a binding in a privileged context:
17030 * <input ng-model="userHtml" aria-label="User input">
17031 * <div ng-bind-html="userHtml"></div>
17034 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
17035 * disabled, this application allows the user to render arbitrary HTML into the DIV.
17036 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
17037 * bindings. (HTML is just one example of a context where rendering user controlled input creates
17038 * security vulnerabilities.)
17040 * For the case of HTML, you might use a library, either on the client side, or on the server side,
17041 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
17043 * How would you ensure that every place that used these types of bindings was bound to a value that
17044 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
17045 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
17046 * properties/fields and forgot to update the binding to the sanitized value?
17048 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
17049 * determine that something explicitly says it's safe to use a value for binding in that
17050 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
17051 * for those values that you can easily tell are safe - because they were received from your server,
17052 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
17053 * allowing only the files in a specific directory to do this. Ensuring that the internal API
17054 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
17056 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
17057 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
17058 * obtain values that will be accepted by SCE / privileged contexts.
17061 * ## How does it work?
17063 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
17064 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
17065 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
17066 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
17068 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
17069 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
17073 * var ngBindHtmlDirective = ['$sce', function($sce) {
17074 * return function(scope, element, attr) {
17075 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17076 * element.html(value || '');
17082 * ## Impact on loading templates
17084 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17085 * `templateUrl`'s specified by {@link guide/directive directives}.
17087 * By default, Angular only loads templates from the same domain and protocol as the application
17088 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17089 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17090 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17091 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17095 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17096 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17097 * policy apply in addition to this and may further restrict whether the template is successfully
17098 * loaded. This means that without the right CORS policy, loading templates from a different domain
17099 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17102 * ## This feels like too much overhead
17104 * It's important to remember that SCE only applies to interpolation expressions.
17106 * If your expressions are constant literals, they're automatically trusted and you don't need to
17107 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17108 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17110 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17111 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17113 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17114 * templates in `ng-include` from your application's domain without having to even know about SCE.
17115 * It blocks loading templates from other domains or loading templates over http from an https
17116 * served document. You can change these by setting your own custom {@link
17117 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17118 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17120 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17121 * application that's secure and can be audited to verify that with much more ease than bolting
17122 * security onto an application later.
17124 * <a name="contexts"></a>
17125 * ## What trusted context types are supported?
17127 * | Context | Notes |
17128 * |---------------------|----------------|
17129 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
17130 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17131 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. |
17132 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
17133 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
17135 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17137 * Each element in these arrays must be one of the following:
17140 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17141 * domain** as the application document using the **same protocol**.
17142 * - **String** (except the special value `'self'`)
17143 * - The string is matched against the full *normalized / absolute URL* of the resource
17144 * being tested (substring matches are not good enough.)
17145 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17146 * match themselves.
17147 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17148 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17150 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17151 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17152 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17153 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17154 * http://foo.example.com/templates/**).
17155 * - **RegExp** (*see caveat below*)
17156 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17157 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17158 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17159 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17160 * small number of cases. A `.` character in the regex used when matching the scheme or a
17161 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17162 * is highly recommended to use the string patterns and only fall back to regular expressions
17163 * as a last resort.
17164 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17165 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17166 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17167 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17168 * - If you are generating your JavaScript from some other templating engine (not
17169 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17170 * remember to escape your regular expression (and be aware that you might need more than
17171 * one level of escaping depending on your templating engine and the way you interpolated
17172 * the value.) Do make use of your platform's escaping mechanism as it might be good
17173 * enough before coding your own. E.g. Ruby has
17174 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17175 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17176 * Javascript lacks a similar built in function for escaping. Take a look at Google
17177 * Closure library's [goog.string.regExpEscape(s)](
17178 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17180 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17182 * ## Show me an example using SCE.
17184 * <example module="mySceApp" deps="angular-sanitize.js">
17185 * <file name="index.html">
17186 * <div ng-controller="AppController as myCtrl">
17187 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17188 * <b>User comments</b><br>
17189 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17190 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17192 * <div class="well">
17193 * <div ng-repeat="userComment in myCtrl.userComments">
17194 * <b>{{userComment.name}}</b>:
17195 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17202 * <file name="script.js">
17203 * angular.module('mySceApp', ['ngSanitize'])
17204 * .controller('AppController', ['$http', '$templateCache', '$sce',
17205 * function($http, $templateCache, $sce) {
17207 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17208 * self.userComments = userComments;
17210 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17211 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17212 * 'sanitization."">Hover over this text.</span>');
17216 * <file name="test_data.json">
17218 * { "name": "Alice",
17220 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17223 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17228 * <file name="protractor.js" type="protractor">
17229 * describe('SCE doc demo', function() {
17230 * it('should sanitize untrusted values', function() {
17231 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17232 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17235 * it('should NOT sanitize explicitly trusted values', function() {
17236 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17237 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17238 * 'sanitization."">Hover over this text.</span>');
17246 * ## Can I disable SCE completely?
17248 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17249 * for little coding overhead. It will be much harder to take an SCE disabled application and
17250 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17251 * for cases where you have a lot of existing code that was written before SCE was introduced and
17252 * you're migrating them a module at a time.
17254 * That said, here's how you can completely disable SCE:
17257 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17258 * // Completely disable SCE. For demonstration purposes only!
17259 * // Do not use in new projects.
17260 * $sceProvider.enabled(false);
17265 /* jshint maxlen: 100 */
17267 function $SceProvider() {
17268 var enabled = true;
17272 * @name $sceProvider#enabled
17275 * @param {boolean=} value If provided, then enables/disables SCE.
17276 * @return {boolean} true if SCE is enabled, false otherwise.
17279 * Enables/disables SCE and returns the current value.
17281 this.enabled = function(value) {
17282 if (arguments.length) {
17289 /* Design notes on the default implementation for SCE.
17291 * The API contract for the SCE delegate
17292 * -------------------------------------
17293 * The SCE delegate object must provide the following 3 methods:
17295 * - trustAs(contextEnum, value)
17296 * This method is used to tell the SCE service that the provided value is OK to use in the
17297 * contexts specified by contextEnum. It must return an object that will be accepted by
17298 * getTrusted() for a compatible contextEnum and return this value.
17301 * For values that were not produced by trustAs(), return them as is. For values that were
17302 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17303 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17306 * - getTrusted(contextEnum, value)
17307 * This function should return the a value that is safe to use in the context specified by
17308 * contextEnum or throw and exception otherwise.
17310 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17311 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17312 * instance, an implementation could maintain a registry of all trusted objects by context. In
17313 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17314 * return the same object passed in if it was found in the registry under a compatible context or
17315 * throw an exception otherwise. An implementation might only wrap values some of the time based
17316 * on some criteria. getTrusted() might return a value and not throw an exception for special
17317 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17320 * A note on the inheritance model for SCE contexts
17321 * ------------------------------------------------
17322 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17323 * is purely an implementation details.
17325 * The contract is simply this:
17327 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17328 * will also succeed.
17330 * Inheritance happens to capture this in a natural way. In some future, we
17331 * may not use inheritance anymore. That is OK because no code outside of
17332 * sce.js and sceSpecs.js would need to be aware of this detail.
17335 this.$get = ['$parse', '$sceDelegate', function(
17336 $parse, $sceDelegate) {
17337 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17338 // the "expression(javascript expression)" syntax which is insecure.
17339 if (enabled && msie < 8) {
17340 throw $sceMinErr('iequirks',
17341 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17342 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17343 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17346 var sce = shallowCopy(SCE_CONTEXTS);
17350 * @name $sce#isEnabled
17353 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17354 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17357 * Returns a boolean indicating if SCE is enabled.
17359 sce.isEnabled = function() {
17362 sce.trustAs = $sceDelegate.trustAs;
17363 sce.getTrusted = $sceDelegate.getTrusted;
17364 sce.valueOf = $sceDelegate.valueOf;
17367 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17368 sce.valueOf = identity;
17373 * @name $sce#parseAs
17376 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17377 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17378 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17381 * @param {string} type The kind of SCE context in which this result will be used.
17382 * @param {string} expression String expression to compile.
17383 * @returns {function(context, locals)} a function which represents the compiled expression:
17385 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17386 * are evaluated against (typically a scope object).
17387 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17390 sce.parseAs = function sceParseAs(type, expr) {
17391 var parsed = $parse(expr);
17392 if (parsed.literal && parsed.constant) {
17395 return $parse(expr, function(value) {
17396 return sce.getTrusted(type, value);
17403 * @name $sce#trustAs
17406 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17407 * returns an object that is trusted by angular for use in specified strict contextual
17408 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17409 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17410 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17413 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17414 * resourceUrl, html, js and css.
17415 * @param {*} value The value that that should be considered trusted/safe.
17416 * @returns {*} A value that can be used to stand in for the provided `value` in places
17417 * where Angular expects a $sce.trustAs() return value.
17422 * @name $sce#trustAsHtml
17425 * Shorthand method. `$sce.trustAsHtml(value)` →
17426 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17428 * @param {*} value The value to trustAs.
17429 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17430 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17431 * only accept expressions that are either literal constants or are the
17432 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17437 * @name $sce#trustAsUrl
17440 * Shorthand method. `$sce.trustAsUrl(value)` →
17441 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17443 * @param {*} value The value to trustAs.
17444 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17445 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17446 * only accept expressions that are either literal constants or are the
17447 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17452 * @name $sce#trustAsResourceUrl
17455 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17456 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17458 * @param {*} value The value to trustAs.
17459 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17460 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17461 * only accept expressions that are either literal constants or are the return
17462 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17467 * @name $sce#trustAsJs
17470 * Shorthand method. `$sce.trustAsJs(value)` →
17471 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17473 * @param {*} value The value to trustAs.
17474 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17475 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17476 * only accept expressions that are either literal constants or are the
17477 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17482 * @name $sce#getTrusted
17485 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17486 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17487 * originally supplied value if the queried context type is a supertype of the created type.
17488 * If this condition isn't satisfied, throws an exception.
17490 * @param {string} type The kind of context in which this value is to be used.
17491 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17493 * @returns {*} The value the was originally provided to
17494 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17495 * Otherwise, throws an exception.
17500 * @name $sce#getTrustedHtml
17503 * Shorthand method. `$sce.getTrustedHtml(value)` →
17504 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17506 * @param {*} value The value to pass to `$sce.getTrusted`.
17507 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17512 * @name $sce#getTrustedCss
17515 * Shorthand method. `$sce.getTrustedCss(value)` →
17516 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17518 * @param {*} value The value to pass to `$sce.getTrusted`.
17519 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17524 * @name $sce#getTrustedUrl
17527 * Shorthand method. `$sce.getTrustedUrl(value)` →
17528 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17530 * @param {*} value The value to pass to `$sce.getTrusted`.
17531 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17536 * @name $sce#getTrustedResourceUrl
17539 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17540 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17542 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17543 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17548 * @name $sce#getTrustedJs
17551 * Shorthand method. `$sce.getTrustedJs(value)` →
17552 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17554 * @param {*} value The value to pass to `$sce.getTrusted`.
17555 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17560 * @name $sce#parseAsHtml
17563 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17564 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17566 * @param {string} expression String expression to compile.
17567 * @returns {function(context, locals)} a function which represents the compiled expression:
17569 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17570 * are evaluated against (typically a scope object).
17571 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17577 * @name $sce#parseAsCss
17580 * Shorthand method. `$sce.parseAsCss(value)` →
17581 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17583 * @param {string} expression String expression to compile.
17584 * @returns {function(context, locals)} a function which represents the compiled expression:
17586 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17587 * are evaluated against (typically a scope object).
17588 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17594 * @name $sce#parseAsUrl
17597 * Shorthand method. `$sce.parseAsUrl(value)` →
17598 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17600 * @param {string} expression String expression to compile.
17601 * @returns {function(context, locals)} a function which represents the compiled expression:
17603 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17604 * are evaluated against (typically a scope object).
17605 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17611 * @name $sce#parseAsResourceUrl
17614 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17615 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17617 * @param {string} expression String expression to compile.
17618 * @returns {function(context, locals)} a function which represents the compiled expression:
17620 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17621 * are evaluated against (typically a scope object).
17622 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17628 * @name $sce#parseAsJs
17631 * Shorthand method. `$sce.parseAsJs(value)` →
17632 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17634 * @param {string} expression String expression to compile.
17635 * @returns {function(context, locals)} a function which represents the compiled expression:
17637 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17638 * are evaluated against (typically a scope object).
17639 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17643 // Shorthand delegations.
17644 var parse = sce.parseAs,
17645 getTrusted = sce.getTrusted,
17646 trustAs = sce.trustAs;
17648 forEach(SCE_CONTEXTS, function(enumValue, name) {
17649 var lName = lowercase(name);
17650 sce[camelCase("parse_as_" + lName)] = function(expr) {
17651 return parse(enumValue, expr);
17653 sce[camelCase("get_trusted_" + lName)] = function(value) {
17654 return getTrusted(enumValue, value);
17656 sce[camelCase("trust_as_" + lName)] = function(value) {
17657 return trustAs(enumValue, value);
17666 * !!! This is an undocumented "private" service !!!
17669 * @requires $window
17670 * @requires $document
17672 * @property {boolean} history Does the browser support html5 history api ?
17673 * @property {boolean} transitions Does the browser support CSS transition events ?
17674 * @property {boolean} animations Does the browser support CSS animation events ?
17677 * This is very simple implementation of testing browser's features.
17679 function $SnifferProvider() {
17680 this.$get = ['$window', '$document', function($window, $document) {
17681 var eventSupport = {},
17683 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17684 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17685 document = $document[0] || {},
17687 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17688 bodyStyle = document.body && document.body.style,
17689 transitions = false,
17690 animations = false,
17694 for (var prop in bodyStyle) {
17695 if (match = vendorRegex.exec(prop)) {
17696 vendorPrefix = match[0];
17697 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17702 if (!vendorPrefix) {
17703 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17706 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17707 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17709 if (android && (!transitions || !animations)) {
17710 transitions = isString(bodyStyle.webkitTransition);
17711 animations = isString(bodyStyle.webkitAnimation);
17717 // Android has history.pushState, but it does not update location correctly
17718 // so let's not use the history API at all.
17719 // http://code.google.com/p/android/issues/detail?id=17471
17720 // https://github.com/angular/angular.js/issues/904
17722 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17723 // so let's not use the history API also
17724 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17726 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17728 hasEvent: function(event) {
17729 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17730 // it. In particular the event is not fired when backspace or delete key are pressed or
17731 // when cut operation is performed.
17732 // IE10+ implements 'input' event but it erroneously fires under various situations,
17733 // e.g. when placeholder changes, or a form is focused.
17734 if (event === 'input' && msie <= 11) return false;
17736 if (isUndefined(eventSupport[event])) {
17737 var divElm = document.createElement('div');
17738 eventSupport[event] = 'on' + event in divElm;
17741 return eventSupport[event];
17744 vendorPrefix: vendorPrefix,
17745 transitions: transitions,
17746 animations: animations,
17752 var $compileMinErr = minErr('$compile');
17756 * @name $templateRequest
17759 * The `$templateRequest` service runs security checks then downloads the provided template using
17760 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17761 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17762 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17763 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17764 * when `tpl` is of type string and `$templateCache` has the matching entry.
17766 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17767 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17769 * @return {Promise} a promise for the HTTP response data of the given URL.
17771 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17773 function $TemplateRequestProvider() {
17774 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17775 function handleRequestFn(tpl, ignoreRequestError) {
17776 handleRequestFn.totalPendingRequests++;
17778 // We consider the template cache holds only trusted templates, so
17779 // there's no need to go through whitelisting again for keys that already
17780 // are included in there. This also makes Angular accept any script
17781 // directive, no matter its name. However, we still need to unwrap trusted
17783 if (!isString(tpl) || !$templateCache.get(tpl)) {
17784 tpl = $sce.getTrustedResourceUrl(tpl);
17787 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17789 if (isArray(transformResponse)) {
17790 transformResponse = transformResponse.filter(function(transformer) {
17791 return transformer !== defaultHttpResponseTransform;
17793 } else if (transformResponse === defaultHttpResponseTransform) {
17794 transformResponse = null;
17797 var httpOptions = {
17798 cache: $templateCache,
17799 transformResponse: transformResponse
17802 return $http.get(tpl, httpOptions)
17803 ['finally'](function() {
17804 handleRequestFn.totalPendingRequests--;
17806 .then(function(response) {
17807 $templateCache.put(tpl, response.data);
17808 return response.data;
17811 function handleError(resp) {
17812 if (!ignoreRequestError) {
17813 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17814 tpl, resp.status, resp.statusText);
17816 return $q.reject(resp);
17820 handleRequestFn.totalPendingRequests = 0;
17822 return handleRequestFn;
17826 function $$TestabilityProvider() {
17827 this.$get = ['$rootScope', '$browser', '$location',
17828 function($rootScope, $browser, $location) {
17831 * @name $testability
17834 * The private $$testability service provides a collection of methods for use when debugging
17835 * or by automated test and debugging tools.
17837 var testability = {};
17840 * @name $$testability#findBindings
17843 * Returns an array of elements that are bound (via ng-bind or {{}})
17844 * to expressions matching the input.
17846 * @param {Element} element The element root to search from.
17847 * @param {string} expression The binding expression to match.
17848 * @param {boolean} opt_exactMatch If true, only returns exact matches
17849 * for the expression. Filters and whitespace are ignored.
17851 testability.findBindings = function(element, expression, opt_exactMatch) {
17852 var bindings = element.getElementsByClassName('ng-binding');
17854 forEach(bindings, function(binding) {
17855 var dataBinding = angular.element(binding).data('$binding');
17857 forEach(dataBinding, function(bindingName) {
17858 if (opt_exactMatch) {
17859 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17860 if (matcher.test(bindingName)) {
17861 matches.push(binding);
17864 if (bindingName.indexOf(expression) != -1) {
17865 matches.push(binding);
17875 * @name $$testability#findModels
17878 * Returns an array of elements that are two-way found via ng-model to
17879 * expressions matching the input.
17881 * @param {Element} element The element root to search from.
17882 * @param {string} expression The model expression to match.
17883 * @param {boolean} opt_exactMatch If true, only returns exact matches
17884 * for the expression.
17886 testability.findModels = function(element, expression, opt_exactMatch) {
17887 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17888 for (var p = 0; p < prefixes.length; ++p) {
17889 var attributeEquals = opt_exactMatch ? '=' : '*=';
17890 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17891 var elements = element.querySelectorAll(selector);
17892 if (elements.length) {
17899 * @name $$testability#getLocation
17902 * Shortcut for getting the location in a browser agnostic way. Returns
17903 * the path, search, and hash. (e.g. /path?a=b#hash)
17905 testability.getLocation = function() {
17906 return $location.url();
17910 * @name $$testability#setLocation
17913 * Shortcut for navigating to a location without doing a full page reload.
17915 * @param {string} url The location url (path, search and hash,
17916 * e.g. /path?a=b#hash) to go to.
17918 testability.setLocation = function(url) {
17919 if (url !== $location.url()) {
17920 $location.url(url);
17921 $rootScope.$digest();
17926 * @name $$testability#whenStable
17929 * Calls the callback when $timeout and $http requests are completed.
17931 * @param {function} callback
17933 testability.whenStable = function(callback) {
17934 $browser.notifyWhenNoOutstandingRequests(callback);
17937 return testability;
17941 function $TimeoutProvider() {
17942 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17943 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17945 var deferreds = {};
17953 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17954 * block and delegates any exceptions to
17955 * {@link ng.$exceptionHandler $exceptionHandler} service.
17957 * The return value of calling `$timeout` is a promise, which will be resolved when
17958 * the delay has passed and the timeout function, if provided, is executed.
17960 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17962 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17963 * synchronously flush the queue of deferred functions.
17965 * If you only want a promise that will be resolved after some specified delay
17966 * then you can call `$timeout` without the `fn` function.
17968 * @param {function()=} fn A function, whose execution should be delayed.
17969 * @param {number=} [delay=0] Delay in milliseconds.
17970 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17971 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17972 * @param {...*=} Pass additional parameters to the executed function.
17973 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17974 * promise will be resolved with is the return value of the `fn` function.
17977 function timeout(fn, delay, invokeApply) {
17978 if (!isFunction(fn)) {
17979 invokeApply = delay;
17984 var args = sliceArgs(arguments, 3),
17985 skipApply = (isDefined(invokeApply) && !invokeApply),
17986 deferred = (skipApply ? $$q : $q).defer(),
17987 promise = deferred.promise,
17990 timeoutId = $browser.defer(function() {
17992 deferred.resolve(fn.apply(null, args));
17994 deferred.reject(e);
17995 $exceptionHandler(e);
17998 delete deferreds[promise.$$timeoutId];
18001 if (!skipApply) $rootScope.$apply();
18004 promise.$$timeoutId = timeoutId;
18005 deferreds[timeoutId] = deferred;
18013 * @name $timeout#cancel
18016 * Cancels a task associated with the `promise`. As a result of this, the promise will be
18017 * resolved with a rejection.
18019 * @param {Promise=} promise Promise returned by the `$timeout` function.
18020 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
18023 timeout.cancel = function(promise) {
18024 if (promise && promise.$$timeoutId in deferreds) {
18025 deferreds[promise.$$timeoutId].reject('canceled');
18026 delete deferreds[promise.$$timeoutId];
18027 return $browser.defer.cancel(promise.$$timeoutId);
18036 // NOTE: The usage of window and document instead of $window and $document here is
18037 // deliberate. This service depends on the specific behavior of anchor nodes created by the
18038 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
18039 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
18040 // doesn't know about mocked locations and resolves URLs to the real document - which is
18041 // exactly the behavior needed here. There is little value is mocking these out for this
18043 var urlParsingNode = document.createElement("a");
18044 var originUrl = urlResolve(window.location.href);
18049 * Implementation Notes for non-IE browsers
18050 * ----------------------------------------
18051 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
18052 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
18053 * URL will be resolved into an absolute URL in the context of the application document.
18054 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
18055 * properties are all populated to reflect the normalized URL. This approach has wide
18056 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
18057 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18059 * Implementation Notes for IE
18060 * ---------------------------
18061 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
18062 * browsers. However, the parsed components will not be set if the URL assigned did not specify
18063 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
18064 * work around that by performing the parsing in a 2nd step by taking a previously normalized
18065 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
18066 * properties such as protocol, hostname, port, etc.
18069 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
18070 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18071 * http://url.spec.whatwg.org/#urlutils
18072 * https://github.com/angular/angular.js/pull/2902
18073 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18076 * @param {string} url The URL to be parsed.
18077 * @description Normalizes and parses a URL.
18078 * @returns {object} Returns the normalized URL as a dictionary.
18080 * | member name | Description |
18081 * |---------------|----------------|
18082 * | href | A normalized version of the provided URL if it was not an absolute URL |
18083 * | protocol | The protocol including the trailing colon |
18084 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18085 * | search | The search params, minus the question mark |
18086 * | hash | The hash string, minus the hash symbol
18087 * | hostname | The hostname
18088 * | port | The port, without ":"
18089 * | pathname | The pathname, beginning with "/"
18092 function urlResolve(url) {
18096 // Normalize before parse. Refer Implementation Notes on why this is
18097 // done in two steps on IE.
18098 urlParsingNode.setAttribute("href", href);
18099 href = urlParsingNode.href;
18102 urlParsingNode.setAttribute('href', href);
18104 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18106 href: urlParsingNode.href,
18107 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18108 host: urlParsingNode.host,
18109 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18110 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18111 hostname: urlParsingNode.hostname,
18112 port: urlParsingNode.port,
18113 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18114 ? urlParsingNode.pathname
18115 : '/' + urlParsingNode.pathname
18120 * Parse a request URL and determine whether this is a same-origin request as the application document.
18122 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18123 * or a parsed URL object.
18124 * @returns {boolean} Whether the request is for the same origin as the application document.
18126 function urlIsSameOrigin(requestUrl) {
18127 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18128 return (parsed.protocol === originUrl.protocol &&
18129 parsed.host === originUrl.host);
18137 * A reference to the browser's `window` object. While `window`
18138 * is globally available in JavaScript, it causes testability problems, because
18139 * it is a global variable. In angular we always refer to it through the
18140 * `$window` service, so it may be overridden, removed or mocked for testing.
18142 * Expressions, like the one defined for the `ngClick` directive in the example
18143 * below, are evaluated with respect to the current scope. Therefore, there is
18144 * no risk of inadvertently coding in a dependency on a global value in such an
18148 <example module="windowExample">
18149 <file name="index.html">
18151 angular.module('windowExample', [])
18152 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18153 $scope.greeting = 'Hello, World!';
18154 $scope.doGreeting = function(greeting) {
18155 $window.alert(greeting);
18159 <div ng-controller="ExampleController">
18160 <input type="text" ng-model="greeting" aria-label="greeting" />
18161 <button ng-click="doGreeting(greeting)">ALERT</button>
18164 <file name="protractor.js" type="protractor">
18165 it('should display the greeting in the input box', function() {
18166 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18167 // If we click the button it will block the test runner
18168 // element(':button').click();
18173 function $WindowProvider() {
18174 this.$get = valueFn(window);
18178 * @name $$cookieReader
18179 * @requires $document
18182 * This is a private service for reading cookies used by $http and ngCookies
18184 * @return {Object} a key/value map of the current cookies
18186 function $$CookieReader($document) {
18187 var rawDocument = $document[0] || {};
18188 var lastCookies = {};
18189 var lastCookieString = '';
18191 function safeDecodeURIComponent(str) {
18193 return decodeURIComponent(str);
18199 return function() {
18200 var cookieArray, cookie, i, index, name;
18201 var currentCookieString = rawDocument.cookie || '';
18203 if (currentCookieString !== lastCookieString) {
18204 lastCookieString = currentCookieString;
18205 cookieArray = lastCookieString.split('; ');
18208 for (i = 0; i < cookieArray.length; i++) {
18209 cookie = cookieArray[i];
18210 index = cookie.indexOf('=');
18211 if (index > 0) { //ignore nameless cookies
18212 name = safeDecodeURIComponent(cookie.substring(0, index));
18213 // the first value that is seen for a cookie is the most
18214 // specific one. values for the same cookie name that
18215 // follow are for less specific paths.
18216 if (isUndefined(lastCookies[name])) {
18217 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18222 return lastCookies;
18226 $$CookieReader.$inject = ['$document'];
18228 function $$CookieReaderProvider() {
18229 this.$get = $$CookieReader;
18232 /* global currencyFilter: true,
18234 filterFilter: true,
18236 limitToFilter: true,
18237 lowercaseFilter: true,
18238 numberFilter: true,
18239 orderByFilter: true,
18240 uppercaseFilter: true,
18245 * @name $filterProvider
18248 * Filters are just functions which transform input to an output. However filters need to be
18249 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18250 * annotated with dependencies and is responsible for creating a filter function.
18252 * <div class="alert alert-warning">
18253 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18254 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18255 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18256 * (`myapp_subsection_filterx`).
18260 * // Filter registration
18261 * function MyModule($provide, $filterProvider) {
18262 * // create a service to demonstrate injection (not always needed)
18263 * $provide.value('greet', function(name){
18264 * return 'Hello ' + name + '!';
18267 * // register a filter factory which uses the
18268 * // greet service to demonstrate DI.
18269 * $filterProvider.register('greet', function(greet){
18270 * // return the filter function which uses the greet service
18271 * // to generate salutation
18272 * return function(text) {
18273 * // filters need to be forgiving so check input validity
18274 * return text && greet(text) || text;
18280 * The filter function is registered with the `$injector` under the filter name suffix with
18284 * it('should be the same instance', inject(
18285 * function($filterProvider) {
18286 * $filterProvider.register('reverse', function(){
18290 * function($filter, reverseFilter) {
18291 * expect($filter('reverse')).toBe(reverseFilter);
18296 * For more information about how angular filters work, and how to create your own filters, see
18297 * {@link guide/filter Filters} in the Angular Developer Guide.
18305 * Filters are used for formatting data displayed to the user.
18307 * The general syntax in templates is as follows:
18309 * {{ expression [| filter_name[:parameter_value] ... ] }}
18311 * @param {String} name Name of the filter function to retrieve
18312 * @return {Function} the filter function
18314 <example name="$filter" module="filterExample">
18315 <file name="index.html">
18316 <div ng-controller="MainCtrl">
18317 <h3>{{ originalText }}</h3>
18318 <h3>{{ filteredText }}</h3>
18322 <file name="script.js">
18323 angular.module('filterExample', [])
18324 .controller('MainCtrl', function($scope, $filter) {
18325 $scope.originalText = 'hello';
18326 $scope.filteredText = $filter('uppercase')($scope.originalText);
18331 $FilterProvider.$inject = ['$provide'];
18332 function $FilterProvider($provide) {
18333 var suffix = 'Filter';
18337 * @name $filterProvider#register
18338 * @param {string|Object} name Name of the filter function, or an object map of filters where
18339 * the keys are the filter names and the values are the filter factories.
18341 * <div class="alert alert-warning">
18342 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18343 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18344 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18345 * (`myapp_subsection_filterx`).
18347 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18348 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18349 * of the registered filter instances.
18351 function register(name, factory) {
18352 if (isObject(name)) {
18354 forEach(name, function(filter, key) {
18355 filters[key] = register(key, filter);
18359 return $provide.factory(name + suffix, factory);
18362 this.register = register;
18364 this.$get = ['$injector', function($injector) {
18365 return function(name) {
18366 return $injector.get(name + suffix);
18370 ////////////////////////////////////////
18373 currencyFilter: false,
18375 filterFilter: false,
18377 limitToFilter: false,
18378 lowercaseFilter: false,
18379 numberFilter: false,
18380 orderByFilter: false,
18381 uppercaseFilter: false,
18384 register('currency', currencyFilter);
18385 register('date', dateFilter);
18386 register('filter', filterFilter);
18387 register('json', jsonFilter);
18388 register('limitTo', limitToFilter);
18389 register('lowercase', lowercaseFilter);
18390 register('number', numberFilter);
18391 register('orderBy', orderByFilter);
18392 register('uppercase', uppercaseFilter);
18401 * Selects a subset of items from `array` and returns it as a new array.
18403 * @param {Array} array The source array.
18404 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18409 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18410 * objects with string properties in `array` that match this string will be returned. This also
18411 * applies to nested object properties.
18412 * The predicate can be negated by prefixing the string with `!`.
18414 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18415 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18416 * which have property `name` containing "M" and property `phone` containing "1". A special
18417 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18418 * property of the object or its nested object properties. That's equivalent to the simple
18419 * substring match with a `string` as described above. The predicate can be negated by prefixing
18420 * the string with `!`.
18421 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18422 * not containing "M".
18424 * Note that a named property will match properties on the same level only, while the special
18425 * `$` property will match properties on the same level or deeper. E.g. an array item like
18426 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18427 * **will** be matched by `{$: 'John'}`.
18429 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18430 * The function is called for each element of the array, with the element, its index, and
18431 * the entire array itself as arguments.
18433 * The final result is an array of those elements that the predicate returned true for.
18435 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18436 * determining if the expected value (from the filter expression) and actual value (from
18437 * the object in the array) should be considered a match.
18441 * - `function(actual, expected)`:
18442 * The function will be given the object value and the predicate value to compare and
18443 * should return true if both values should be considered equal.
18445 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18446 * This is essentially strict comparison of expected and actual.
18448 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18451 * Primitive values are converted to strings. Objects are not compared against primitives,
18452 * unless they have a custom `toString` method (e.g. `Date` objects).
18456 <file name="index.html">
18457 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18458 {name:'Mary', phone:'800-BIG-MARY'},
18459 {name:'Mike', phone:'555-4321'},
18460 {name:'Adam', phone:'555-5678'},
18461 {name:'Julie', phone:'555-8765'},
18462 {name:'Juliette', phone:'555-5678'}]"></div>
18464 <label>Search: <input ng-model="searchText"></label>
18465 <table id="searchTextResults">
18466 <tr><th>Name</th><th>Phone</th></tr>
18467 <tr ng-repeat="friend in friends | filter:searchText">
18468 <td>{{friend.name}}</td>
18469 <td>{{friend.phone}}</td>
18473 <label>Any: <input ng-model="search.$"></label> <br>
18474 <label>Name only <input ng-model="search.name"></label><br>
18475 <label>Phone only <input ng-model="search.phone"></label><br>
18476 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18477 <table id="searchObjResults">
18478 <tr><th>Name</th><th>Phone</th></tr>
18479 <tr ng-repeat="friendObj in friends | filter:search:strict">
18480 <td>{{friendObj.name}}</td>
18481 <td>{{friendObj.phone}}</td>
18485 <file name="protractor.js" type="protractor">
18486 var expectFriendNames = function(expectedNames, key) {
18487 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18488 arr.forEach(function(wd, i) {
18489 expect(wd.getText()).toMatch(expectedNames[i]);
18494 it('should search across all fields when filtering with a string', function() {
18495 var searchText = element(by.model('searchText'));
18496 searchText.clear();
18497 searchText.sendKeys('m');
18498 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18500 searchText.clear();
18501 searchText.sendKeys('76');
18502 expectFriendNames(['John', 'Julie'], 'friend');
18505 it('should search in specific fields when filtering with a predicate object', function() {
18506 var searchAny = element(by.model('search.$'));
18508 searchAny.sendKeys('i');
18509 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18511 it('should use a equal comparison when comparator is true', function() {
18512 var searchName = element(by.model('search.name'));
18513 var strict = element(by.model('strict'));
18514 searchName.clear();
18515 searchName.sendKeys('Julie');
18517 expectFriendNames(['Julie'], 'friendObj');
18522 function filterFilter() {
18523 return function(array, expression, comparator) {
18524 if (!isArrayLike(array)) {
18525 if (array == null) {
18528 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18532 var expressionType = getTypeForFilter(expression);
18534 var matchAgainstAnyProp;
18536 switch (expressionType) {
18538 predicateFn = expression;
18544 matchAgainstAnyProp = true;
18548 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18554 return Array.prototype.filter.call(array, predicateFn);
18558 // Helper functions for `filterFilter`
18559 function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18560 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18563 if (comparator === true) {
18564 comparator = equals;
18565 } else if (!isFunction(comparator)) {
18566 comparator = function(actual, expected) {
18567 if (isUndefined(actual)) {
18568 // No substring matching against `undefined`
18571 if ((actual === null) || (expected === null)) {
18572 // No substring matching against `null`; only match against `null`
18573 return actual === expected;
18575 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18576 // Should not compare primitives against objects, unless they have custom `toString` method
18580 actual = lowercase('' + actual);
18581 expected = lowercase('' + expected);
18582 return actual.indexOf(expected) !== -1;
18586 predicateFn = function(item) {
18587 if (shouldMatchPrimitives && !isObject(item)) {
18588 return deepCompare(item, expression.$, comparator, false);
18590 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18593 return predicateFn;
18596 function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18597 var actualType = getTypeForFilter(actual);
18598 var expectedType = getTypeForFilter(expected);
18600 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18601 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18602 } else if (isArray(actual)) {
18603 // In case `actual` is an array, consider it a match
18604 // if ANY of it's items matches `expected`
18605 return actual.some(function(item) {
18606 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18610 switch (actualType) {
18613 if (matchAgainstAnyProp) {
18614 for (key in actual) {
18615 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18619 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18620 } else if (expectedType === 'object') {
18621 for (key in expected) {
18622 var expectedVal = expected[key];
18623 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18627 var matchAnyProperty = key === '$';
18628 var actualVal = matchAnyProperty ? actual : actual[key];
18629 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18635 return comparator(actual, expected);
18641 return comparator(actual, expected);
18645 // Used for easily differentiating between `null` and actual `object`
18646 function getTypeForFilter(val) {
18647 return (val === null) ? 'null' : typeof val;
18656 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18657 * symbol for current locale is used.
18659 * @param {number} amount Input to filter.
18660 * @param {string=} symbol Currency symbol or identifier to be displayed.
18661 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18662 * @returns {string} Formatted number.
18666 <example module="currencyExample">
18667 <file name="index.html">
18669 angular.module('currencyExample', [])
18670 .controller('ExampleController', ['$scope', function($scope) {
18671 $scope.amount = 1234.56;
18674 <div ng-controller="ExampleController">
18675 <input type="number" ng-model="amount" aria-label="amount"> <br>
18676 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18677 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18678 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18681 <file name="protractor.js" type="protractor">
18682 it('should init with 1234.56', function() {
18683 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18684 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18685 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18687 it('should update', function() {
18688 if (browser.params.browser == 'safari') {
18689 // Safari does not understand the minus key. See
18690 // https://github.com/angular/protractor/issues/481
18693 element(by.model('amount')).clear();
18694 element(by.model('amount')).sendKeys('-1234');
18695 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18696 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18697 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18702 currencyFilter.$inject = ['$locale'];
18703 function currencyFilter($locale) {
18704 var formats = $locale.NUMBER_FORMATS;
18705 return function(amount, currencySymbol, fractionSize) {
18706 if (isUndefined(currencySymbol)) {
18707 currencySymbol = formats.CURRENCY_SYM;
18710 if (isUndefined(fractionSize)) {
18711 fractionSize = formats.PATTERNS[1].maxFrac;
18714 // if null or undefined pass it through
18715 return (amount == null)
18717 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18718 replace(/\u00A4/g, currencySymbol);
18728 * Formats a number as text.
18730 * If the input is null or undefined, it will just be returned.
18731 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18732 * If the input is not a number an empty string is returned.
18735 * @param {number|string} number Number to format.
18736 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18737 * If this is not provided then the fraction size is computed from the current locale's number
18738 * formatting pattern. In the case of the default locale, it will be 3.
18739 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18742 <example module="numberFilterExample">
18743 <file name="index.html">
18745 angular.module('numberFilterExample', [])
18746 .controller('ExampleController', ['$scope', function($scope) {
18747 $scope.val = 1234.56789;
18750 <div ng-controller="ExampleController">
18751 <label>Enter number: <input ng-model='val'></label><br>
18752 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18753 No fractions: <span>{{val | number:0}}</span><br>
18754 Negative number: <span>{{-val | number:4}}</span>
18757 <file name="protractor.js" type="protractor">
18758 it('should format numbers', function() {
18759 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18760 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18761 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18764 it('should update', function() {
18765 element(by.model('val')).clear();
18766 element(by.model('val')).sendKeys('3374.333');
18767 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18768 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18769 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18776 numberFilter.$inject = ['$locale'];
18777 function numberFilter($locale) {
18778 var formats = $locale.NUMBER_FORMATS;
18779 return function(number, fractionSize) {
18781 // if null or undefined pass it through
18782 return (number == null)
18784 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18789 var DECIMAL_SEP = '.';
18790 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18791 if (isObject(number)) return '';
18793 var isNegative = number < 0;
18794 number = Math.abs(number);
18796 var isInfinity = number === Infinity;
18797 if (!isInfinity && !isFinite(number)) return '';
18799 var numStr = number + '',
18801 hasExponent = false,
18804 if (isInfinity) formatedText = '\u221e';
18806 if (!isInfinity && numStr.indexOf('e') !== -1) {
18807 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18808 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18811 formatedText = numStr;
18812 hasExponent = true;
18816 if (!isInfinity && !hasExponent) {
18817 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18819 // determine fractionSize if it is not specified
18820 if (isUndefined(fractionSize)) {
18821 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18824 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18826 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18827 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18829 var fraction = ('' + number).split(DECIMAL_SEP);
18830 var whole = fraction[0];
18831 fraction = fraction[1] || '';
18834 lgroup = pattern.lgSize,
18835 group = pattern.gSize;
18837 if (whole.length >= (lgroup + group)) {
18838 pos = whole.length - lgroup;
18839 for (i = 0; i < pos; i++) {
18840 if ((pos - i) % group === 0 && i !== 0) {
18841 formatedText += groupSep;
18843 formatedText += whole.charAt(i);
18847 for (i = pos; i < whole.length; i++) {
18848 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18849 formatedText += groupSep;
18851 formatedText += whole.charAt(i);
18854 // format fraction part.
18855 while (fraction.length < fractionSize) {
18859 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18861 if (fractionSize > 0 && number < 1) {
18862 formatedText = number.toFixed(fractionSize);
18863 number = parseFloat(formatedText);
18864 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18868 if (number === 0) {
18869 isNegative = false;
18872 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18874 isNegative ? pattern.negSuf : pattern.posSuf);
18875 return parts.join('');
18878 function padNumber(num, digits, trim) {
18885 while (num.length < digits) num = '0' + num;
18887 num = num.substr(num.length - digits);
18893 function dateGetter(name, size, offset, trim) {
18894 offset = offset || 0;
18895 return function(date) {
18896 var value = date['get' + name]();
18897 if (offset > 0 || value > -offset) {
18900 if (value === 0 && offset == -12) value = 12;
18901 return padNumber(value, size, trim);
18905 function dateStrGetter(name, shortForm) {
18906 return function(date, formats) {
18907 var value = date['get' + name]();
18908 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18910 return formats[get][value];
18914 function timeZoneGetter(date, formats, offset) {
18915 var zone = -1 * offset;
18916 var paddedZone = (zone >= 0) ? "+" : "";
18918 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18919 padNumber(Math.abs(zone % 60), 2);
18924 function getFirstThursdayOfYear(year) {
18925 // 0 = index of January
18926 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18927 // 4 = index of Thursday (+1 to account for 1st = 5)
18928 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18929 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18932 function getThursdayThisWeek(datetime) {
18933 return new Date(datetime.getFullYear(), datetime.getMonth(),
18934 // 4 = index of Thursday
18935 datetime.getDate() + (4 - datetime.getDay()));
18938 function weekGetter(size) {
18939 return function(date) {
18940 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18941 thisThurs = getThursdayThisWeek(date);
18943 var diff = +thisThurs - +firstThurs,
18944 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18946 return padNumber(result, size);
18950 function ampmGetter(date, formats) {
18951 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18954 function eraGetter(date, formats) {
18955 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18958 function longEraGetter(date, formats) {
18959 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18962 var DATE_FORMATS = {
18963 yyyy: dateGetter('FullYear', 4),
18964 yy: dateGetter('FullYear', 2, 0, true),
18965 y: dateGetter('FullYear', 1),
18966 MMMM: dateStrGetter('Month'),
18967 MMM: dateStrGetter('Month', true),
18968 MM: dateGetter('Month', 2, 1),
18969 M: dateGetter('Month', 1, 1),
18970 dd: dateGetter('Date', 2),
18971 d: dateGetter('Date', 1),
18972 HH: dateGetter('Hours', 2),
18973 H: dateGetter('Hours', 1),
18974 hh: dateGetter('Hours', 2, -12),
18975 h: dateGetter('Hours', 1, -12),
18976 mm: dateGetter('Minutes', 2),
18977 m: dateGetter('Minutes', 1),
18978 ss: dateGetter('Seconds', 2),
18979 s: dateGetter('Seconds', 1),
18980 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18981 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18982 sss: dateGetter('Milliseconds', 3),
18983 EEEE: dateStrGetter('Day'),
18984 EEE: dateStrGetter('Day', true),
18992 GGGG: longEraGetter
18995 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
18996 NUMBER_STRING = /^\-?\d+$/;
19004 * Formats `date` to a string based on the requested `format`.
19006 * `format` string can be composed of the following elements:
19008 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
19009 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
19010 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
19011 * * `'MMMM'`: Month in year (January-December)
19012 * * `'MMM'`: Month in year (Jan-Dec)
19013 * * `'MM'`: Month in year, padded (01-12)
19014 * * `'M'`: Month in year (1-12)
19015 * * `'dd'`: Day in month, padded (01-31)
19016 * * `'d'`: Day in month (1-31)
19017 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
19018 * * `'EEE'`: Day in Week, (Sun-Sat)
19019 * * `'HH'`: Hour in day, padded (00-23)
19020 * * `'H'`: Hour in day (0-23)
19021 * * `'hh'`: Hour in AM/PM, padded (01-12)
19022 * * `'h'`: Hour in AM/PM, (1-12)
19023 * * `'mm'`: Minute in hour, padded (00-59)
19024 * * `'m'`: Minute in hour (0-59)
19025 * * `'ss'`: Second in minute, padded (00-59)
19026 * * `'s'`: Second in minute (0-59)
19027 * * `'sss'`: Millisecond in second, padded (000-999)
19028 * * `'a'`: AM/PM marker
19029 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
19030 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
19031 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19032 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
19033 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
19035 * `format` string can also be one of the following predefined
19036 * {@link guide/i18n localizable formats}:
19038 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
19039 * (e.g. Sep 3, 2010 12:05:08 PM)
19040 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
19041 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
19042 * (e.g. Friday, September 3, 2010)
19043 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
19044 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
19045 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
19046 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
19047 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
19049 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
19050 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
19051 * (e.g. `"h 'o''clock'"`).
19053 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
19054 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
19055 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
19056 * specified in the string input, the time is considered to be in the local timezone.
19057 * @param {string=} format Formatting rules (see Description). If not specified,
19058 * `mediumDate` is used.
19059 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
19060 * continental US time zone abbreviations, but for general use, use a time zone offset, for
19061 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
19062 * If not specified, the timezone of the browser will be used.
19063 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
19067 <file name="index.html">
19068 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
19069 <span>{{1288323623006 | date:'medium'}}</span><br>
19070 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
19071 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
19072 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
19073 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19074 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19075 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19077 <file name="protractor.js" type="protractor">
19078 it('should format date', function() {
19079 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19080 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19081 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19082 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19083 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19084 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19085 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19086 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19091 dateFilter.$inject = ['$locale'];
19092 function dateFilter($locale) {
19095 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19096 // 1 2 3 4 5 6 7 8 9 10 11
19097 function jsonStringToDate(string) {
19099 if (match = string.match(R_ISO8601_STR)) {
19100 var date = new Date(0),
19103 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19104 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19107 tzHour = toInt(match[9] + match[10]);
19108 tzMin = toInt(match[9] + match[11]);
19110 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19111 var h = toInt(match[4] || 0) - tzHour;
19112 var m = toInt(match[5] || 0) - tzMin;
19113 var s = toInt(match[6] || 0);
19114 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19115 timeSetter.call(date, h, m, s, ms);
19122 return function(date, format, timezone) {
19127 format = format || 'mediumDate';
19128 format = $locale.DATETIME_FORMATS[format] || format;
19129 if (isString(date)) {
19130 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19133 if (isNumber(date)) {
19134 date = new Date(date);
19137 if (!isDate(date) || !isFinite(date.getTime())) {
19142 match = DATE_FORMATS_SPLIT.exec(format);
19144 parts = concat(parts, match, 1);
19145 format = parts.pop();
19147 parts.push(format);
19152 var dateTimezoneOffset = date.getTimezoneOffset();
19154 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19155 date = convertTimezoneToLocal(date, timezone, true);
19157 forEach(parts, function(value) {
19158 fn = DATE_FORMATS[value];
19159 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19160 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19174 * Allows you to convert a JavaScript object into JSON string.
19176 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19177 * the binding is automatically converted to JSON.
19179 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19180 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19181 * @returns {string} JSON string.
19186 <file name="index.html">
19187 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19188 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19190 <file name="protractor.js" type="protractor">
19191 it('should jsonify filtered objects', function() {
19192 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19193 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19199 function jsonFilter() {
19200 return function(object, spacing) {
19201 if (isUndefined(spacing)) {
19204 return toJson(object, spacing);
19214 * Converts string to lowercase.
19215 * @see angular.lowercase
19217 var lowercaseFilter = valueFn(lowercase);
19225 * Converts string to uppercase.
19226 * @see angular.uppercase
19228 var uppercaseFilter = valueFn(uppercase);
19236 * Creates a new array or string containing only a specified number of elements. The elements
19237 * are taken from either the beginning or the end of the source array, string or number, as specified by
19238 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19239 * converted to a string.
19241 * @param {Array|string|number} input Source array, string or number to be limited.
19242 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19243 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19244 * If the number is negative, `limit` number of items from the end of the source array/string
19245 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19246 * the input will be returned unchanged.
19247 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19248 * indicates an offset from the end of `input`. Defaults to `0`.
19249 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19250 * had less than `limit` elements.
19253 <example module="limitToExample">
19254 <file name="index.html">
19256 angular.module('limitToExample', [])
19257 .controller('ExampleController', ['$scope', function($scope) {
19258 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19259 $scope.letters = "abcdefghi";
19260 $scope.longNumber = 2345432342;
19261 $scope.numLimit = 3;
19262 $scope.letterLimit = 3;
19263 $scope.longNumberLimit = 3;
19266 <div ng-controller="ExampleController">
19268 Limit {{numbers}} to:
19269 <input type="number" step="1" ng-model="numLimit">
19271 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19273 Limit {{letters}} to:
19274 <input type="number" step="1" ng-model="letterLimit">
19276 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19278 Limit {{longNumber}} to:
19279 <input type="number" step="1" ng-model="longNumberLimit">
19281 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19284 <file name="protractor.js" type="protractor">
19285 var numLimitInput = element(by.model('numLimit'));
19286 var letterLimitInput = element(by.model('letterLimit'));
19287 var longNumberLimitInput = element(by.model('longNumberLimit'));
19288 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19289 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19290 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19292 it('should limit the number array to first three items', function() {
19293 expect(numLimitInput.getAttribute('value')).toBe('3');
19294 expect(letterLimitInput.getAttribute('value')).toBe('3');
19295 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19296 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19297 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19298 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19301 // There is a bug in safari and protractor that doesn't like the minus key
19302 // it('should update the output when -3 is entered', function() {
19303 // numLimitInput.clear();
19304 // numLimitInput.sendKeys('-3');
19305 // letterLimitInput.clear();
19306 // letterLimitInput.sendKeys('-3');
19307 // longNumberLimitInput.clear();
19308 // longNumberLimitInput.sendKeys('-3');
19309 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19310 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19311 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19314 it('should not exceed the maximum size of input array', function() {
19315 numLimitInput.clear();
19316 numLimitInput.sendKeys('100');
19317 letterLimitInput.clear();
19318 letterLimitInput.sendKeys('100');
19319 longNumberLimitInput.clear();
19320 longNumberLimitInput.sendKeys('100');
19321 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19322 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19323 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19328 function limitToFilter() {
19329 return function(input, limit, begin) {
19330 if (Math.abs(Number(limit)) === Infinity) {
19331 limit = Number(limit);
19333 limit = toInt(limit);
19335 if (isNaN(limit)) return input;
19337 if (isNumber(input)) input = input.toString();
19338 if (!isArray(input) && !isString(input)) return input;
19340 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19341 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19344 return input.slice(begin, begin + limit);
19347 return input.slice(limit, input.length);
19349 return input.slice(Math.max(0, begin + limit), begin);
19361 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19362 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19363 * as expected, make sure they are actually being saved as numbers and not strings.
19365 * @param {Array} array The array to sort.
19366 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19367 * used by the comparator to determine the order of elements.
19371 * - `function`: Getter function. The result of this function will be sorted using the
19372 * `<`, `===`, `>` operator.
19373 * - `string`: An Angular expression. The result of this expression is used to compare elements
19374 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19375 * 3 first characters of a property called `name`). The result of a constant expression
19376 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19377 * to sort object by the value of their `special name` property). An expression can be
19378 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19379 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19380 * element itself is used to compare where sorting.
19381 * - `Array`: An array of function or string predicates. The first predicate in the array
19382 * is used for sorting, but when two items are equivalent, the next predicate is used.
19384 * If the predicate is missing or empty then it defaults to `'+'`.
19386 * @param {boolean=} reverse Reverse the order of the array.
19387 * @returns {Array} Sorted copy of the source array.
19391 * The example below demonstrates a simple ngRepeat, where the data is sorted
19392 * by age in descending order (predicate is set to `'-age'`).
19393 * `reverse` is not set, which means it defaults to `false`.
19394 <example module="orderByExample">
19395 <file name="index.html">
19397 angular.module('orderByExample', [])
19398 .controller('ExampleController', ['$scope', function($scope) {
19400 [{name:'John', phone:'555-1212', age:10},
19401 {name:'Mary', phone:'555-9876', age:19},
19402 {name:'Mike', phone:'555-4321', age:21},
19403 {name:'Adam', phone:'555-5678', age:35},
19404 {name:'Julie', phone:'555-8765', age:29}];
19407 <div ng-controller="ExampleController">
19408 <table class="friend">
19411 <th>Phone Number</th>
19414 <tr ng-repeat="friend in friends | orderBy:'-age'">
19415 <td>{{friend.name}}</td>
19416 <td>{{friend.phone}}</td>
19417 <td>{{friend.age}}</td>
19424 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19425 * as shown in the next example.
19427 <example module="orderByExample">
19428 <file name="index.html">
19430 angular.module('orderByExample', [])
19431 .controller('ExampleController', ['$scope', function($scope) {
19433 [{name:'John', phone:'555-1212', age:10},
19434 {name:'Mary', phone:'555-9876', age:19},
19435 {name:'Mike', phone:'555-4321', age:21},
19436 {name:'Adam', phone:'555-5678', age:35},
19437 {name:'Julie', phone:'555-8765', age:29}];
19438 $scope.predicate = 'age';
19439 $scope.reverse = true;
19440 $scope.order = function(predicate) {
19441 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19442 $scope.predicate = predicate;
19446 <style type="text/css">
19450 .sortorder.reverse:after {
19454 <div ng-controller="ExampleController">
19455 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19457 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19458 <table class="friend">
19461 <a href="" ng-click="order('name')">Name</a>
19462 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19465 <a href="" ng-click="order('phone')">Phone Number</a>
19466 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19469 <a href="" ng-click="order('age')">Age</a>
19470 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19473 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19474 <td>{{friend.name}}</td>
19475 <td>{{friend.phone}}</td>
19476 <td>{{friend.age}}</td>
19483 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19484 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19485 * desired parameters.
19490 <example module="orderByExample">
19491 <file name="index.html">
19492 <div ng-controller="ExampleController">
19493 <table class="friend">
19495 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19496 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19497 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19498 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19500 <tr ng-repeat="friend in friends">
19501 <td>{{friend.name}}</td>
19502 <td>{{friend.phone}}</td>
19503 <td>{{friend.age}}</td>
19509 <file name="script.js">
19510 angular.module('orderByExample', [])
19511 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19512 var orderBy = $filter('orderBy');
19514 { name: 'John', phone: '555-1212', age: 10 },
19515 { name: 'Mary', phone: '555-9876', age: 19 },
19516 { name: 'Mike', phone: '555-4321', age: 21 },
19517 { name: 'Adam', phone: '555-5678', age: 35 },
19518 { name: 'Julie', phone: '555-8765', age: 29 }
19520 $scope.order = function(predicate, reverse) {
19521 $scope.friends = orderBy($scope.friends, predicate, reverse);
19523 $scope.order('-age',false);
19528 orderByFilter.$inject = ['$parse'];
19529 function orderByFilter($parse) {
19530 return function(array, sortPredicate, reverseOrder) {
19532 if (!(isArrayLike(array))) return array;
19534 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19535 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19537 var predicates = processPredicates(sortPredicate, reverseOrder);
19538 // Add a predicate at the end that evaluates to the element index. This makes the
19539 // sort stable as it works as a tie-breaker when all the input predicates cannot
19540 // distinguish between two elements.
19541 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19543 // The next three lines are a version of a Swartzian Transform idiom from Perl
19544 // (sometimes called the Decorate-Sort-Undecorate idiom)
19545 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19546 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19547 compareValues.sort(doComparison);
19548 array = compareValues.map(function(item) { return item.value; });
19552 function getComparisonObject(value, index) {
19555 predicateValues: predicates.map(function(predicate) {
19556 return getPredicateValue(predicate.get(value), index);
19561 function doComparison(v1, v2) {
19563 for (var index=0, length = predicates.length; index < length; ++index) {
19564 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19571 function processPredicates(sortPredicate, reverseOrder) {
19572 reverseOrder = reverseOrder ? -1 : 1;
19573 return sortPredicate.map(function(predicate) {
19574 var descending = 1, get = identity;
19576 if (isFunction(predicate)) {
19578 } else if (isString(predicate)) {
19579 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19580 descending = predicate.charAt(0) == '-' ? -1 : 1;
19581 predicate = predicate.substring(1);
19583 if (predicate !== '') {
19584 get = $parse(predicate);
19585 if (get.constant) {
19587 get = function(value) { return value[key]; };
19591 return { get: get, descending: descending * reverseOrder };
19595 function isPrimitive(value) {
19596 switch (typeof value) {
19597 case 'number': /* falls through */
19598 case 'boolean': /* falls through */
19606 function objectValue(value, index) {
19607 // If `valueOf` is a valid function use that
19608 if (typeof value.valueOf === 'function') {
19609 value = value.valueOf();
19610 if (isPrimitive(value)) return value;
19612 // If `toString` is a valid function and not the one from `Object.prototype` use that
19613 if (hasCustomToString(value)) {
19614 value = value.toString();
19615 if (isPrimitive(value)) return value;
19617 // We have a basic object so we use the position of the object in the collection
19621 function getPredicateValue(value, index) {
19622 var type = typeof value;
19623 if (value === null) {
19626 } else if (type === 'string') {
19627 value = value.toLowerCase();
19628 } else if (type === 'object') {
19629 value = objectValue(value, index);
19631 return { value: value, type: type };
19634 function compare(v1, v2) {
19636 if (v1.type === v2.type) {
19637 if (v1.value !== v2.value) {
19638 result = v1.value < v2.value ? -1 : 1;
19641 result = v1.type < v2.type ? -1 : 1;
19647 function ngDirective(directive) {
19648 if (isFunction(directive)) {
19653 directive.restrict = directive.restrict || 'AC';
19654 return valueFn(directive);
19663 * Modifies the default behavior of the html A tag so that the default action is prevented when
19664 * the href attribute is empty.
19666 * This change permits the easy creation of action links with the `ngClick` directive
19667 * without changing the location or causing page reloads, e.g.:
19668 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19670 var htmlAnchorDirective = valueFn({
19672 compile: function(element, attr) {
19673 if (!attr.href && !attr.xlinkHref) {
19674 return function(scope, element) {
19675 // If the linked element is not an anchor tag anymore, do nothing
19676 if (element[0].nodeName.toLowerCase() !== 'a') return;
19678 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19679 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19680 'xlink:href' : 'href';
19681 element.on('click', function(event) {
19682 // if we have no href url, then don't navigate anywhere.
19683 if (!element.attr(href)) {
19684 event.preventDefault();
19699 * Using Angular markup like `{{hash}}` in an href attribute will
19700 * make the link go to the wrong URL if the user clicks it before
19701 * Angular has a chance to replace the `{{hash}}` markup with its
19702 * value. Until Angular replaces the markup the link will be broken
19703 * and will most likely return a 404 error. The `ngHref` directive
19704 * solves this problem.
19706 * The wrong way to write it:
19708 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19711 * The correct way to write it:
19713 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19717 * @param {template} ngHref any string which can contain `{{}}` markup.
19720 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19721 * in links and their different behaviors:
19723 <file name="index.html">
19724 <input ng-model="value" /><br />
19725 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19726 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19727 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19728 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19729 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19730 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19732 <file name="protractor.js" type="protractor">
19733 it('should execute ng-click but not reload when href without value', function() {
19734 element(by.id('link-1')).click();
19735 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19736 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19739 it('should execute ng-click but not reload when href empty string', function() {
19740 element(by.id('link-2')).click();
19741 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19742 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19745 it('should execute ng-click and change url when ng-href specified', function() {
19746 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19748 element(by.id('link-3')).click();
19750 // At this point, we navigate away from an Angular page, so we need
19751 // to use browser.driver to get the base webdriver.
19753 browser.wait(function() {
19754 return browser.driver.getCurrentUrl().then(function(url) {
19755 return url.match(/\/123$/);
19757 }, 5000, 'page should navigate to /123');
19760 it('should execute ng-click but not reload when href empty string and name specified', function() {
19761 element(by.id('link-4')).click();
19762 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19763 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19766 it('should execute ng-click but not reload when no href but name specified', function() {
19767 element(by.id('link-5')).click();
19768 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19769 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19772 it('should only change url when only ng-href', function() {
19773 element(by.model('value')).clear();
19774 element(by.model('value')).sendKeys('6');
19775 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19777 element(by.id('link-6')).click();
19779 // At this point, we navigate away from an Angular page, so we need
19780 // to use browser.driver to get the base webdriver.
19781 browser.wait(function() {
19782 return browser.driver.getCurrentUrl().then(function(url) {
19783 return url.match(/\/6$/);
19785 }, 5000, 'page should navigate to /6');
19798 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19799 * work right: The browser will fetch from the URL with the literal
19800 * text `{{hash}}` until Angular replaces the expression inside
19801 * `{{hash}}`. The `ngSrc` directive solves this problem.
19803 * The buggy way to write it:
19805 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19808 * The correct way to write it:
19810 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19814 * @param {template} ngSrc any string which can contain `{{}}` markup.
19824 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19825 * work right: The browser will fetch from the URL with the literal
19826 * text `{{hash}}` until Angular replaces the expression inside
19827 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19829 * The buggy way to write it:
19831 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19834 * The correct way to write it:
19836 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19840 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19851 * This directive sets the `disabled` attribute on the element if the
19852 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19854 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19855 * attribute. The following example would make the button enabled on Chrome/Firefox
19856 * but not on older IEs:
19859 * <!-- See below for an example of ng-disabled being used correctly -->
19860 * <div ng-init="isDisabled = false">
19861 * <button disabled="{{isDisabled}}">Disabled</button>
19865 * This is because the HTML specification does not require browsers to preserve the values of
19866 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19867 * If we put an Angular interpolation expression into such an attribute then the
19868 * binding information would be lost when the browser removes the attribute.
19872 <file name="index.html">
19873 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19874 <button ng-model="button" ng-disabled="checked">Button</button>
19876 <file name="protractor.js" type="protractor">
19877 it('should toggle button', function() {
19878 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19879 element(by.model('checked')).click();
19880 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19886 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19887 * then the `disabled` attribute will be set on the element
19898 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19900 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19901 * as this can lead to unexpected behavior.
19903 * ### Why do we need `ngChecked`?
19905 * The HTML specification does not require browsers to preserve the values of boolean attributes
19906 * such as checked. (Their presence means true and their absence means false.)
19907 * If we put an Angular interpolation expression into such an attribute then the
19908 * binding information would be lost when the browser removes the attribute.
19909 * The `ngChecked` directive solves this problem for the `checked` attribute.
19910 * This complementary directive is not removed by the browser and so provides
19911 * a permanent reliable place to store the binding information.
19914 <file name="index.html">
19915 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19916 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19918 <file name="protractor.js" type="protractor">
19919 it('should check both checkBoxes', function() {
19920 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19921 element(by.model('master')).click();
19922 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19928 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19929 * then the `checked` attribute will be set on the element
19940 * The HTML specification does not require browsers to preserve the values of boolean attributes
19941 * such as readonly. (Their presence means true and their absence means false.)
19942 * If we put an Angular interpolation expression into such an attribute then the
19943 * binding information would be lost when the browser removes the attribute.
19944 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19945 * This complementary directive is not removed by the browser and so provides
19946 * a permanent reliable place to store the binding information.
19949 <file name="index.html">
19950 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19951 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19953 <file name="protractor.js" type="protractor">
19954 it('should toggle readonly attr', function() {
19955 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19956 element(by.model('checked')).click();
19957 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19963 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19964 * then special attribute "readonly" will be set on the element
19975 * The HTML specification does not require browsers to preserve the values of boolean attributes
19976 * such as selected. (Their presence means true and their absence means false.)
19977 * If we put an Angular interpolation expression into such an attribute then the
19978 * binding information would be lost when the browser removes the attribute.
19979 * The `ngSelected` directive solves this problem for the `selected` attribute.
19980 * This complementary directive is not removed by the browser and so provides
19981 * a permanent reliable place to store the binding information.
19985 <file name="index.html">
19986 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19987 <select aria-label="ngSelected demo">
19988 <option>Hello!</option>
19989 <option id="greet" ng-selected="selected">Greetings!</option>
19992 <file name="protractor.js" type="protractor">
19993 it('should select Greetings!', function() {
19994 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19995 element(by.model('selected')).click();
19996 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
20002 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
20003 * then special attribute "selected" will be set on the element
20013 * The HTML specification does not require browsers to preserve the values of boolean attributes
20014 * such as open. (Their presence means true and their absence means false.)
20015 * If we put an Angular interpolation expression into such an attribute then the
20016 * binding information would be lost when the browser removes the attribute.
20017 * The `ngOpen` directive solves this problem for the `open` attribute.
20018 * This complementary directive is not removed by the browser and so provides
20019 * a permanent reliable place to store the binding information.
20022 <file name="index.html">
20023 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
20024 <details id="details" ng-open="open">
20025 <summary>Show/Hide me</summary>
20028 <file name="protractor.js" type="protractor">
20029 it('should toggle open', function() {
20030 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20031 element(by.model('open')).click();
20032 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
20038 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
20039 * then special attribute "open" will be set on the element
20042 var ngAttributeAliasDirectives = {};
20044 // boolean attrs are evaluated
20045 forEach(BOOLEAN_ATTR, function(propName, attrName) {
20046 // binding to multiple is not supported
20047 if (propName == "multiple") return;
20049 function defaultLinkFn(scope, element, attr) {
20050 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
20051 attr.$set(attrName, !!value);
20055 var normalized = directiveNormalize('ng-' + attrName);
20056 var linkFn = defaultLinkFn;
20058 if (propName === 'checked') {
20059 linkFn = function(scope, element, attr) {
20060 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
20061 if (attr.ngModel !== attr[normalized]) {
20062 defaultLinkFn(scope, element, attr);
20067 ngAttributeAliasDirectives[normalized] = function() {
20076 // aliased input attrs are evaluated
20077 forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20078 ngAttributeAliasDirectives[ngAttr] = function() {
20081 link: function(scope, element, attr) {
20082 //special case ngPattern when a literal regular expression value
20083 //is used as the expression (this way we don't have to watch anything).
20084 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20085 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20087 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20092 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20093 attr.$set(ngAttr, value);
20100 // ng-src, ng-srcset, ng-href are interpolated
20101 forEach(['src', 'srcset', 'href'], function(attrName) {
20102 var normalized = directiveNormalize('ng-' + attrName);
20103 ngAttributeAliasDirectives[normalized] = function() {
20105 priority: 99, // it needs to run after the attributes are interpolated
20106 link: function(scope, element, attr) {
20107 var propName = attrName,
20110 if (attrName === 'href' &&
20111 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20112 name = 'xlinkHref';
20113 attr.$attr[name] = 'xlink:href';
20117 attr.$observe(normalized, function(value) {
20119 if (attrName === 'href') {
20120 attr.$set(name, null);
20125 attr.$set(name, value);
20127 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20128 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20129 // to set the property as well to achieve the desired effect.
20130 // we use attr[attrName] value since $set can sanitize the url.
20131 if (msie && propName) element.prop(propName, attr[name]);
20138 /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20140 var nullFormCtrl = {
20142 $$renameControl: nullFormRenameControl,
20143 $removeControl: noop,
20144 $setValidity: noop,
20146 $setPristine: noop,
20147 $setSubmitted: noop
20149 SUBMITTED_CLASS = 'ng-submitted';
20151 function nullFormRenameControl(control, name) {
20152 control.$name = name;
20157 * @name form.FormController
20159 * @property {boolean} $pristine True if user has not interacted with the form yet.
20160 * @property {boolean} $dirty True if user has already interacted with the form.
20161 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20162 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20163 * @property {boolean} $pending True if at least one containing control or form is pending.
20164 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20166 * @property {Object} $error Is an object hash, containing references to controls or
20167 * forms with failing validators, where:
20169 * - keys are validation tokens (error names),
20170 * - values are arrays of controls or forms that have a failing validator for given error name.
20172 * Built-in validation tokens:
20184 * - `datetimelocal`
20190 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20191 * such as being valid/invalid or dirty/pristine.
20193 * Each {@link ng.directive:form form} directive creates an instance
20194 * of `FormController`.
20197 //asks for $scope to fool the BC controller module
20198 FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20199 function FormController(element, attrs, $scope, $animate, $interpolate) {
20205 form.$$success = {};
20206 form.$pending = undefined;
20207 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20208 form.$dirty = false;
20209 form.$pristine = true;
20210 form.$valid = true;
20211 form.$invalid = false;
20212 form.$submitted = false;
20213 form.$$parentForm = nullFormCtrl;
20217 * @name form.FormController#$rollbackViewValue
20220 * Rollback all form controls pending updates to the `$modelValue`.
20222 * Updates may be pending by a debounced event or because the input is waiting for a some future
20223 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20224 * a form that uses `ng-model-options` to pend updates.
20226 form.$rollbackViewValue = function() {
20227 forEach(controls, function(control) {
20228 control.$rollbackViewValue();
20234 * @name form.FormController#$commitViewValue
20237 * Commit all form controls pending updates to the `$modelValue`.
20239 * Updates may be pending by a debounced event or because the input is waiting for a some future
20240 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20241 * usually handles calling this in response to input events.
20243 form.$commitViewValue = function() {
20244 forEach(controls, function(control) {
20245 control.$commitViewValue();
20251 * @name form.FormController#$addControl
20252 * @param {object} control control object, either a {@link form.FormController} or an
20253 * {@link ngModel.NgModelController}
20256 * Register a control with the form. Input elements using ngModelController do this automatically
20257 * when they are linked.
20259 * Note that the current state of the control will not be reflected on the new parent form. This
20260 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20263 * However, if the method is used programmatically, for example by adding dynamically created controls,
20264 * or controls that have been previously removed without destroying their corresponding DOM element,
20265 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20267 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20268 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20270 form.$addControl = function(control) {
20271 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20272 // and not added to the scope. Now we throw an error.
20273 assertNotHasOwnProperty(control.$name, 'input');
20274 controls.push(control);
20276 if (control.$name) {
20277 form[control.$name] = control;
20280 control.$$parentForm = form;
20283 // Private API: rename a form control
20284 form.$$renameControl = function(control, newName) {
20285 var oldName = control.$name;
20287 if (form[oldName] === control) {
20288 delete form[oldName];
20290 form[newName] = control;
20291 control.$name = newName;
20296 * @name form.FormController#$removeControl
20297 * @param {object} control control object, either a {@link form.FormController} or an
20298 * {@link ngModel.NgModelController}
20301 * Deregister a control from the form.
20303 * Input elements using ngModelController do this automatically when they are destroyed.
20305 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20306 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20307 * different from case to case. For example, removing the only `$dirty` control from a form may or
20308 * may not mean that the form is still `$dirty`.
20310 form.$removeControl = function(control) {
20311 if (control.$name && form[control.$name] === control) {
20312 delete form[control.$name];
20314 forEach(form.$pending, function(value, name) {
20315 form.$setValidity(name, null, control);
20317 forEach(form.$error, function(value, name) {
20318 form.$setValidity(name, null, control);
20320 forEach(form.$$success, function(value, name) {
20321 form.$setValidity(name, null, control);
20324 arrayRemove(controls, control);
20325 control.$$parentForm = nullFormCtrl;
20331 * @name form.FormController#$setValidity
20334 * Sets the validity of a form control.
20336 * This method will also propagate to parent forms.
20338 addSetValidityMethod({
20341 set: function(object, property, controller) {
20342 var list = object[property];
20344 object[property] = [controller];
20346 var index = list.indexOf(controller);
20347 if (index === -1) {
20348 list.push(controller);
20352 unset: function(object, property, controller) {
20353 var list = object[property];
20357 arrayRemove(list, controller);
20358 if (list.length === 0) {
20359 delete object[property];
20367 * @name form.FormController#$setDirty
20370 * Sets the form to a dirty state.
20372 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20373 * state (ng-dirty class). This method will also propagate to parent forms.
20375 form.$setDirty = function() {
20376 $animate.removeClass(element, PRISTINE_CLASS);
20377 $animate.addClass(element, DIRTY_CLASS);
20378 form.$dirty = true;
20379 form.$pristine = false;
20380 form.$$parentForm.$setDirty();
20385 * @name form.FormController#$setPristine
20388 * Sets the form to its pristine state.
20390 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20391 * state (ng-pristine class). This method will also propagate to all the controls contained
20394 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20395 * saving or resetting it.
20397 form.$setPristine = function() {
20398 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20399 form.$dirty = false;
20400 form.$pristine = true;
20401 form.$submitted = false;
20402 forEach(controls, function(control) {
20403 control.$setPristine();
20409 * @name form.FormController#$setUntouched
20412 * Sets the form to its untouched state.
20414 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20415 * untouched state (ng-untouched class).
20417 * Setting a form controls back to their untouched state is often useful when setting the form
20418 * back to its pristine state.
20420 form.$setUntouched = function() {
20421 forEach(controls, function(control) {
20422 control.$setUntouched();
20428 * @name form.FormController#$setSubmitted
20431 * Sets the form to its submitted state.
20433 form.$setSubmitted = function() {
20434 $animate.addClass(element, SUBMITTED_CLASS);
20435 form.$submitted = true;
20436 form.$$parentForm.$setSubmitted();
20446 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20447 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20448 * sub-group of controls needs to be determined.
20450 * Note: the purpose of `ngForm` is to group controls,
20451 * but not to be a replacement for the `<form>` tag with all of its capabilities
20452 * (e.g. posting to the server, ...).
20454 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20455 * related scope, under this name.
20465 * Directive that instantiates
20466 * {@link form.FormController FormController}.
20468 * If the `name` attribute is specified, the form controller is published onto the current scope under
20471 * # Alias: {@link ng.directive:ngForm `ngForm`}
20473 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20474 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20475 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20476 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20477 * using Angular validation directives in forms that are dynamically generated using the
20478 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20479 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20480 * `ngForm` directive and nest these in an outer `form` element.
20484 * - `ng-valid` is set if the form is valid.
20485 * - `ng-invalid` is set if the form is invalid.
20486 * - `ng-pending` is set if the form is pending.
20487 * - `ng-pristine` is set if the form is pristine.
20488 * - `ng-dirty` is set if the form is dirty.
20489 * - `ng-submitted` is set if the form was submitted.
20491 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20494 * # Submitting a form and preventing the default action
20496 * Since the role of forms in client-side Angular applications is different than in classical
20497 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20498 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20499 * to handle the form submission in an application-specific way.
20501 * For this reason, Angular prevents the default action (form submission to the server) unless the
20502 * `<form>` element has an `action` attribute specified.
20504 * You can use one of the following two ways to specify what javascript method should be called when
20505 * a form is submitted:
20507 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20508 * - {@link ng.directive:ngClick ngClick} directive on the first
20509 * button or input field of type submit (input[type=submit])
20511 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20512 * or {@link ng.directive:ngClick ngClick} directives.
20513 * This is because of the following form submission rules in the HTML specification:
20515 * - If a form has only one input field then hitting enter in this field triggers form submit
20517 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20518 * doesn't trigger submit
20519 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20520 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20521 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20523 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20524 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20525 * to have access to the updated model.
20527 * ## Animation Hooks
20529 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20530 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20531 * other validations that are performed within the form. Animations in ngForm are similar to how
20532 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20533 * as JS animations.
20535 * The following example shows a simple way to utilize CSS transitions to style a form element
20536 * that has been rendered as invalid after it has been validated:
20539 * //be sure to include ngAnimate as a module to hook into more
20540 * //advanced animations
20542 * transition:0.5s linear all;
20543 * background: white;
20545 * .my-form.ng-invalid {
20552 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20553 <file name="index.html">
20555 angular.module('formExample', [])
20556 .controller('FormController', ['$scope', function($scope) {
20557 $scope.userType = 'guest';
20562 transition:all linear 0.5s;
20563 background: transparent;
20565 .my-form.ng-invalid {
20569 <form name="myForm" ng-controller="FormController" class="my-form">
20570 userType: <input name="input" ng-model="userType" required>
20571 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20572 <code>userType = {{userType}}</code><br>
20573 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20574 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20575 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20576 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20579 <file name="protractor.js" type="protractor">
20580 it('should initialize to model', function() {
20581 var userType = element(by.binding('userType'));
20582 var valid = element(by.binding('myForm.input.$valid'));
20584 expect(userType.getText()).toContain('guest');
20585 expect(valid.getText()).toContain('true');
20588 it('should be invalid if empty', function() {
20589 var userType = element(by.binding('userType'));
20590 var valid = element(by.binding('myForm.input.$valid'));
20591 var userInput = element(by.model('userType'));
20594 userInput.sendKeys('');
20596 expect(userType.getText()).toEqual('userType =');
20597 expect(valid.getText()).toContain('false');
20602 * @param {string=} name Name of the form. If specified, the form controller will be published into
20603 * related scope, under this name.
20605 var formDirectiveFactory = function(isNgForm) {
20606 return ['$timeout', '$parse', function($timeout, $parse) {
20607 var formDirective = {
20609 restrict: isNgForm ? 'EAC' : 'E',
20610 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20611 controller: FormController,
20612 compile: function ngFormCompile(formElement, attr) {
20613 // Setup initial state of the control
20614 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20616 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20619 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20620 var controller = ctrls[0];
20622 // if `action` attr is not present on the form, prevent the default action (submission)
20623 if (!('action' in attr)) {
20624 // we can't use jq events because if a form is destroyed during submission the default
20625 // action is not prevented. see #1238
20627 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20628 // page reload if the form was destroyed by submission of the form via a click handler
20629 // on a button in the form. Looks like an IE9 specific bug.
20630 var handleFormSubmission = function(event) {
20631 scope.$apply(function() {
20632 controller.$commitViewValue();
20633 controller.$setSubmitted();
20636 event.preventDefault();
20639 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20641 // unregister the preventDefault listener so that we don't not leak memory but in a
20642 // way that will achieve the prevention of the default action.
20643 formElement.on('$destroy', function() {
20644 $timeout(function() {
20645 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20650 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20651 parentFormCtrl.$addControl(controller);
20653 var setter = nameAttr ? getSetter(controller.$name) : noop;
20656 setter(scope, controller);
20657 attr.$observe(nameAttr, function(newValue) {
20658 if (controller.$name === newValue) return;
20659 setter(scope, undefined);
20660 controller.$$parentForm.$$renameControl(controller, newValue);
20661 setter = getSetter(controller.$name);
20662 setter(scope, controller);
20665 formElement.on('$destroy', function() {
20666 controller.$$parentForm.$removeControl(controller);
20667 setter(scope, undefined);
20668 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20675 return formDirective;
20677 function getSetter(expression) {
20678 if (expression === '') {
20679 //create an assignable expression, so forms with an empty name can be renamed later
20680 return $parse('this[""]').assign;
20682 return $parse(expression).assign || noop;
20687 var formDirective = formDirectiveFactory();
20688 var ngFormDirective = formDirectiveFactory(true);
20690 /* global VALID_CLASS: false,
20691 INVALID_CLASS: false,
20692 PRISTINE_CLASS: false,
20693 DIRTY_CLASS: false,
20694 UNTOUCHED_CLASS: false,
20695 TOUCHED_CLASS: false,
20696 ngModelMinErr: false,
20699 // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20700 var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
20701 // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20702 var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20703 var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
20704 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20705 var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20706 var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20707 var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20708 var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20709 var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20715 * @name input[text]
20718 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20721 * @param {string} ngModel Assignable angular expression to data-bind to.
20722 * @param {string=} name Property name of the form under which the control is published.
20723 * @param {string=} required Adds `required` validation error key if the value is not entered.
20724 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20725 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20726 * `required` when you want to data-bind to the `required` attribute.
20727 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20729 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20730 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20732 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20733 * that contains the regular expression body that will be converted to a regular expression
20734 * as in the ngPattern directive.
20735 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20736 * a RegExp found by evaluating the Angular expression given in the attribute value.
20737 * If the expression evaluates to a RegExp object, then this is used directly.
20738 * If the expression evaluates to a string, then it will be converted to a RegExp
20739 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20740 * `new RegExp('^abc$')`.<br />
20741 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20742 * start at the index of the last search's match, thus not taking the whole input value into
20744 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20745 * interaction with the input element.
20746 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20747 * This parameter is ignored for input[type=password] controls, which will never trim the
20751 <example name="text-input-directive" module="textInputExample">
20752 <file name="index.html">
20754 angular.module('textInputExample', [])
20755 .controller('ExampleController', ['$scope', function($scope) {
20758 word: /^\s*\w*\s*$/
20762 <form name="myForm" ng-controller="ExampleController">
20763 <label>Single word:
20764 <input type="text" name="input" ng-model="example.text"
20765 ng-pattern="example.word" required ng-trim="false">
20768 <span class="error" ng-show="myForm.input.$error.required">
20770 <span class="error" ng-show="myForm.input.$error.pattern">
20771 Single word only!</span>
20773 <tt>text = {{example.text}}</tt><br/>
20774 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20775 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20776 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20777 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20780 <file name="protractor.js" type="protractor">
20781 var text = element(by.binding('example.text'));
20782 var valid = element(by.binding('myForm.input.$valid'));
20783 var input = element(by.model('example.text'));
20785 it('should initialize to model', function() {
20786 expect(text.getText()).toContain('guest');
20787 expect(valid.getText()).toContain('true');
20790 it('should be invalid if empty', function() {
20792 input.sendKeys('');
20794 expect(text.getText()).toEqual('text =');
20795 expect(valid.getText()).toContain('false');
20798 it('should be invalid if multi word', function() {
20800 input.sendKeys('hello world');
20802 expect(valid.getText()).toContain('false');
20807 'text': textInputType,
20811 * @name input[date]
20814 * Input with date validation and transformation. In browsers that do not yet support
20815 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20816 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20817 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20818 * expected input format via a placeholder or label.
20820 * The model must always be a Date object, otherwise Angular will throw an error.
20821 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20823 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20824 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20826 * @param {string} ngModel Assignable angular expression to data-bind to.
20827 * @param {string=} name Property name of the form under which the control is published.
20828 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20829 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20830 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20831 * constraint validation.
20832 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20833 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20834 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20835 * constraint validation.
20836 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20837 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20838 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20839 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20840 * @param {string=} required Sets `required` validation error key if the value is not entered.
20841 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20842 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20843 * `required` when you want to data-bind to the `required` attribute.
20844 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20845 * interaction with the input element.
20848 <example name="date-input-directive" module="dateInputExample">
20849 <file name="index.html">
20851 angular.module('dateInputExample', [])
20852 .controller('DateController', ['$scope', function($scope) {
20854 value: new Date(2013, 9, 22)
20858 <form name="myForm" ng-controller="DateController as dateCtrl">
20859 <label for="exampleInput">Pick a date in 2013:</label>
20860 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20861 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20863 <span class="error" ng-show="myForm.input.$error.required">
20865 <span class="error" ng-show="myForm.input.$error.date">
20866 Not a valid date!</span>
20868 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20869 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20870 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20871 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20872 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20875 <file name="protractor.js" type="protractor">
20876 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20877 var valid = element(by.binding('myForm.input.$valid'));
20878 var input = element(by.model('example.value'));
20880 // currently protractor/webdriver does not support
20881 // sending keys to all known HTML5 input controls
20882 // for various browsers (see https://github.com/angular/protractor/issues/562).
20883 function setInput(val) {
20884 // set the value of the element and force validation.
20885 var scr = "var ipt = document.getElementById('exampleInput'); " +
20886 "ipt.value = '" + val + "';" +
20887 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20888 browser.executeScript(scr);
20891 it('should initialize to model', function() {
20892 expect(value.getText()).toContain('2013-10-22');
20893 expect(valid.getText()).toContain('myForm.input.$valid = true');
20896 it('should be invalid if empty', function() {
20898 expect(value.getText()).toEqual('value =');
20899 expect(valid.getText()).toContain('myForm.input.$valid = false');
20902 it('should be invalid if over max', function() {
20903 setInput('2015-01-01');
20904 expect(value.getText()).toContain('');
20905 expect(valid.getText()).toContain('myForm.input.$valid = false');
20910 'date': createDateInputType('date', DATE_REGEXP,
20911 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20916 * @name input[datetime-local]
20919 * Input with datetime validation and transformation. In browsers that do not yet support
20920 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20921 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20923 * The model must always be a Date object, otherwise Angular will throw an error.
20924 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20926 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20927 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20929 * @param {string} ngModel Assignable angular expression to data-bind to.
20930 * @param {string=} name Property name of the form under which the control is published.
20931 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20932 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20933 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20934 * Note that `min` will also add native HTML5 constraint validation.
20935 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20936 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20937 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20938 * Note that `max` will also add native HTML5 constraint validation.
20939 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20940 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20941 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20942 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20943 * @param {string=} required Sets `required` validation error key if the value is not entered.
20944 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20945 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20946 * `required` when you want to data-bind to the `required` attribute.
20947 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20948 * interaction with the input element.
20951 <example name="datetimelocal-input-directive" module="dateExample">
20952 <file name="index.html">
20954 angular.module('dateExample', [])
20955 .controller('DateController', ['$scope', function($scope) {
20957 value: new Date(2010, 11, 28, 14, 57)
20961 <form name="myForm" ng-controller="DateController as dateCtrl">
20962 <label for="exampleInput">Pick a date between in 2013:</label>
20963 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20964 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20966 <span class="error" ng-show="myForm.input.$error.required">
20968 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20969 Not a valid date!</span>
20971 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20972 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20973 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20974 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20975 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20978 <file name="protractor.js" type="protractor">
20979 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20980 var valid = element(by.binding('myForm.input.$valid'));
20981 var input = element(by.model('example.value'));
20983 // currently protractor/webdriver does not support
20984 // sending keys to all known HTML5 input controls
20985 // for various browsers (https://github.com/angular/protractor/issues/562).
20986 function setInput(val) {
20987 // set the value of the element and force validation.
20988 var scr = "var ipt = document.getElementById('exampleInput'); " +
20989 "ipt.value = '" + val + "';" +
20990 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20991 browser.executeScript(scr);
20994 it('should initialize to model', function() {
20995 expect(value.getText()).toContain('2010-12-28T14:57:00');
20996 expect(valid.getText()).toContain('myForm.input.$valid = true');
20999 it('should be invalid if empty', function() {
21001 expect(value.getText()).toEqual('value =');
21002 expect(valid.getText()).toContain('myForm.input.$valid = false');
21005 it('should be invalid if over max', function() {
21006 setInput('2015-01-01T23:59:00');
21007 expect(value.getText()).toContain('');
21008 expect(valid.getText()).toContain('myForm.input.$valid = false');
21013 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
21014 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
21015 'yyyy-MM-ddTHH:mm:ss.sss'),
21019 * @name input[time]
21022 * Input with time validation and transformation. In browsers that do not yet support
21023 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21024 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
21025 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
21027 * The model must always be a Date object, otherwise Angular will throw an error.
21028 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21030 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21031 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21033 * @param {string} ngModel Assignable angular expression to data-bind to.
21034 * @param {string=} name Property name of the form under which the control is published.
21035 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21036 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21037 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
21038 * native HTML5 constraint validation.
21039 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21040 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21041 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
21042 * native HTML5 constraint validation.
21043 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
21044 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21045 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
21046 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21047 * @param {string=} required Sets `required` validation error key if the value is not entered.
21048 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21049 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21050 * `required` when you want to data-bind to the `required` attribute.
21051 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21052 * interaction with the input element.
21055 <example name="time-input-directive" module="timeExample">
21056 <file name="index.html">
21058 angular.module('timeExample', [])
21059 .controller('DateController', ['$scope', function($scope) {
21061 value: new Date(1970, 0, 1, 14, 57, 0)
21065 <form name="myForm" ng-controller="DateController as dateCtrl">
21066 <label for="exampleInput">Pick a between 8am and 5pm:</label>
21067 <input type="time" id="exampleInput" name="input" ng-model="example.value"
21068 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
21070 <span class="error" ng-show="myForm.input.$error.required">
21072 <span class="error" ng-show="myForm.input.$error.time">
21073 Not a valid date!</span>
21075 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21076 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21077 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21078 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21079 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21082 <file name="protractor.js" type="protractor">
21083 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21084 var valid = element(by.binding('myForm.input.$valid'));
21085 var input = element(by.model('example.value'));
21087 // currently protractor/webdriver does not support
21088 // sending keys to all known HTML5 input controls
21089 // for various browsers (https://github.com/angular/protractor/issues/562).
21090 function setInput(val) {
21091 // set the value of the element and force validation.
21092 var scr = "var ipt = document.getElementById('exampleInput'); " +
21093 "ipt.value = '" + val + "';" +
21094 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21095 browser.executeScript(scr);
21098 it('should initialize to model', function() {
21099 expect(value.getText()).toContain('14:57:00');
21100 expect(valid.getText()).toContain('myForm.input.$valid = true');
21103 it('should be invalid if empty', function() {
21105 expect(value.getText()).toEqual('value =');
21106 expect(valid.getText()).toContain('myForm.input.$valid = false');
21109 it('should be invalid if over max', function() {
21110 setInput('23:59:00');
21111 expect(value.getText()).toContain('');
21112 expect(valid.getText()).toContain('myForm.input.$valid = false');
21117 'time': createDateInputType('time', TIME_REGEXP,
21118 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21123 * @name input[week]
21126 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21127 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21128 * week format (yyyy-W##), for example: `2013-W02`.
21130 * The model must always be a Date object, otherwise Angular will throw an error.
21131 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21133 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21134 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21136 * @param {string} ngModel Assignable angular expression to data-bind to.
21137 * @param {string=} name Property name of the form under which the control is published.
21138 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21139 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21140 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21141 * native HTML5 constraint validation.
21142 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21143 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21144 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21145 * native HTML5 constraint validation.
21146 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21147 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21148 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21149 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21150 * @param {string=} required Sets `required` validation error key if the value is not entered.
21151 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21152 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21153 * `required` when you want to data-bind to the `required` attribute.
21154 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21155 * interaction with the input element.
21158 <example name="week-input-directive" module="weekExample">
21159 <file name="index.html">
21161 angular.module('weekExample', [])
21162 .controller('DateController', ['$scope', function($scope) {
21164 value: new Date(2013, 0, 3)
21168 <form name="myForm" ng-controller="DateController as dateCtrl">
21169 <label>Pick a date between in 2013:
21170 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21171 placeholder="YYYY-W##" min="2012-W32"
21172 max="2013-W52" required />
21175 <span class="error" ng-show="myForm.input.$error.required">
21177 <span class="error" ng-show="myForm.input.$error.week">
21178 Not a valid date!</span>
21180 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21181 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21182 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21183 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21184 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21187 <file name="protractor.js" type="protractor">
21188 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21189 var valid = element(by.binding('myForm.input.$valid'));
21190 var input = element(by.model('example.value'));
21192 // currently protractor/webdriver does not support
21193 // sending keys to all known HTML5 input controls
21194 // for various browsers (https://github.com/angular/protractor/issues/562).
21195 function setInput(val) {
21196 // set the value of the element and force validation.
21197 var scr = "var ipt = document.getElementById('exampleInput'); " +
21198 "ipt.value = '" + val + "';" +
21199 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21200 browser.executeScript(scr);
21203 it('should initialize to model', function() {
21204 expect(value.getText()).toContain('2013-W01');
21205 expect(valid.getText()).toContain('myForm.input.$valid = true');
21208 it('should be invalid if empty', function() {
21210 expect(value.getText()).toEqual('value =');
21211 expect(valid.getText()).toContain('myForm.input.$valid = false');
21214 it('should be invalid if over max', function() {
21215 setInput('2015-W01');
21216 expect(value.getText()).toContain('');
21217 expect(valid.getText()).toContain('myForm.input.$valid = false');
21222 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21226 * @name input[month]
21229 * Input with month validation and transformation. In browsers that do not yet support
21230 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21231 * month format (yyyy-MM), for example: `2009-01`.
21233 * The model must always be a Date object, otherwise Angular will throw an error.
21234 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21235 * If the model is not set to the first of the month, the next view to model update will set it
21236 * to the first of the month.
21238 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21239 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21241 * @param {string} ngModel Assignable angular expression to data-bind to.
21242 * @param {string=} name Property name of the form under which the control is published.
21243 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21244 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21245 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21246 * native HTML5 constraint validation.
21247 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21248 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21249 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21250 * native HTML5 constraint validation.
21251 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21252 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21253 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21254 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21256 * @param {string=} required Sets `required` validation error key if the value is not entered.
21257 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21258 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21259 * `required` when you want to data-bind to the `required` attribute.
21260 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21261 * interaction with the input element.
21264 <example name="month-input-directive" module="monthExample">
21265 <file name="index.html">
21267 angular.module('monthExample', [])
21268 .controller('DateController', ['$scope', function($scope) {
21270 value: new Date(2013, 9, 1)
21274 <form name="myForm" ng-controller="DateController as dateCtrl">
21275 <label for="exampleInput">Pick a month in 2013:</label>
21276 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21277 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21279 <span class="error" ng-show="myForm.input.$error.required">
21281 <span class="error" ng-show="myForm.input.$error.month">
21282 Not a valid month!</span>
21284 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21285 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21286 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21287 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21288 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21291 <file name="protractor.js" type="protractor">
21292 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21293 var valid = element(by.binding('myForm.input.$valid'));
21294 var input = element(by.model('example.value'));
21296 // currently protractor/webdriver does not support
21297 // sending keys to all known HTML5 input controls
21298 // for various browsers (https://github.com/angular/protractor/issues/562).
21299 function setInput(val) {
21300 // set the value of the element and force validation.
21301 var scr = "var ipt = document.getElementById('exampleInput'); " +
21302 "ipt.value = '" + val + "';" +
21303 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21304 browser.executeScript(scr);
21307 it('should initialize to model', function() {
21308 expect(value.getText()).toContain('2013-10');
21309 expect(valid.getText()).toContain('myForm.input.$valid = true');
21312 it('should be invalid if empty', function() {
21314 expect(value.getText()).toEqual('value =');
21315 expect(valid.getText()).toContain('myForm.input.$valid = false');
21318 it('should be invalid if over max', function() {
21319 setInput('2015-01');
21320 expect(value.getText()).toContain('');
21321 expect(valid.getText()).toContain('myForm.input.$valid = false');
21326 'month': createDateInputType('month', MONTH_REGEXP,
21327 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21332 * @name input[number]
21335 * Text input with number validation and transformation. Sets the `number` validation
21336 * error if not a valid number.
21338 * <div class="alert alert-warning">
21339 * The model must always be of type `number` otherwise Angular will throw an error.
21340 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21341 * error docs for more information and an example of how to convert your model if necessary.
21344 * ## Issues with HTML5 constraint validation
21346 * In browsers that follow the
21347 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21348 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21349 * If a non-number is entered in the input, the browser will report the value as an empty string,
21350 * which means the view / model values in `ngModel` and subsequently the scope value
21351 * will also be an empty string.
21354 * @param {string} ngModel Assignable angular expression to data-bind to.
21355 * @param {string=} name Property name of the form under which the control is published.
21356 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21357 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21358 * @param {string=} required Sets `required` validation error key if the value is not entered.
21359 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21360 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21361 * `required` when you want to data-bind to the `required` attribute.
21362 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21364 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21365 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21367 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21368 * that contains the regular expression body that will be converted to a regular expression
21369 * as in the ngPattern directive.
21370 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21371 * a RegExp found by evaluating the Angular expression given in the attribute value.
21372 * If the expression evaluates to a RegExp object, then this is used directly.
21373 * If the expression evaluates to a string, then it will be converted to a RegExp
21374 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21375 * `new RegExp('^abc$')`.<br />
21376 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21377 * start at the index of the last search's match, thus not taking the whole input value into
21379 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21380 * interaction with the input element.
21383 <example name="number-input-directive" module="numberExample">
21384 <file name="index.html">
21386 angular.module('numberExample', [])
21387 .controller('ExampleController', ['$scope', function($scope) {
21393 <form name="myForm" ng-controller="ExampleController">
21395 <input type="number" name="input" ng-model="example.value"
21396 min="0" max="99" required>
21399 <span class="error" ng-show="myForm.input.$error.required">
21401 <span class="error" ng-show="myForm.input.$error.number">
21402 Not valid number!</span>
21404 <tt>value = {{example.value}}</tt><br/>
21405 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21406 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21407 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21408 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21411 <file name="protractor.js" type="protractor">
21412 var value = element(by.binding('example.value'));
21413 var valid = element(by.binding('myForm.input.$valid'));
21414 var input = element(by.model('example.value'));
21416 it('should initialize to model', function() {
21417 expect(value.getText()).toContain('12');
21418 expect(valid.getText()).toContain('true');
21421 it('should be invalid if empty', function() {
21423 input.sendKeys('');
21424 expect(value.getText()).toEqual('value =');
21425 expect(valid.getText()).toContain('false');
21428 it('should be invalid if over max', function() {
21430 input.sendKeys('123');
21431 expect(value.getText()).toEqual('value =');
21432 expect(valid.getText()).toContain('false');
21437 'number': numberInputType,
21445 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21448 * <div class="alert alert-warning">
21449 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21450 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21451 * the built-in validators (see the {@link guide/forms Forms guide})
21454 * @param {string} ngModel Assignable angular expression to data-bind to.
21455 * @param {string=} name Property name of the form under which the control is published.
21456 * @param {string=} required Sets `required` validation error key if the value is not entered.
21457 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21458 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21459 * `required` when you want to data-bind to the `required` attribute.
21460 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21462 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21463 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21465 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21466 * that contains the regular expression body that will be converted to a regular expression
21467 * as in the ngPattern directive.
21468 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21469 * a RegExp found by evaluating the Angular expression given in the attribute value.
21470 * If the expression evaluates to a RegExp object, then this is used directly.
21471 * If the expression evaluates to a string, then it will be converted to a RegExp
21472 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21473 * `new RegExp('^abc$')`.<br />
21474 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21475 * start at the index of the last search's match, thus not taking the whole input value into
21477 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21478 * interaction with the input element.
21481 <example name="url-input-directive" module="urlExample">
21482 <file name="index.html">
21484 angular.module('urlExample', [])
21485 .controller('ExampleController', ['$scope', function($scope) {
21487 text: 'http://google.com'
21491 <form name="myForm" ng-controller="ExampleController">
21493 <input type="url" name="input" ng-model="url.text" required>
21496 <span class="error" ng-show="myForm.input.$error.required">
21498 <span class="error" ng-show="myForm.input.$error.url">
21499 Not valid url!</span>
21501 <tt>text = {{url.text}}</tt><br/>
21502 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21503 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21504 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21505 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21506 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21509 <file name="protractor.js" type="protractor">
21510 var text = element(by.binding('url.text'));
21511 var valid = element(by.binding('myForm.input.$valid'));
21512 var input = element(by.model('url.text'));
21514 it('should initialize to model', function() {
21515 expect(text.getText()).toContain('http://google.com');
21516 expect(valid.getText()).toContain('true');
21519 it('should be invalid if empty', function() {
21521 input.sendKeys('');
21523 expect(text.getText()).toEqual('text =');
21524 expect(valid.getText()).toContain('false');
21527 it('should be invalid if not url', function() {
21529 input.sendKeys('box');
21531 expect(valid.getText()).toContain('false');
21536 'url': urlInputType,
21541 * @name input[email]
21544 * Text input with email validation. Sets the `email` validation error key if not a valid email
21547 * <div class="alert alert-warning">
21548 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21549 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21550 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21553 * @param {string} ngModel Assignable angular expression to data-bind to.
21554 * @param {string=} name Property name of the form under which the control is published.
21555 * @param {string=} required Sets `required` validation error key if the value is not entered.
21556 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21557 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21558 * `required` when you want to data-bind to the `required` attribute.
21559 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21561 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21562 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21564 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21565 * that contains the regular expression body that will be converted to a regular expression
21566 * as in the ngPattern directive.
21567 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21568 * a RegExp found by evaluating the Angular expression given in the attribute value.
21569 * If the expression evaluates to a RegExp object, then this is used directly.
21570 * If the expression evaluates to a string, then it will be converted to a RegExp
21571 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21572 * `new RegExp('^abc$')`.<br />
21573 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21574 * start at the index of the last search's match, thus not taking the whole input value into
21576 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21577 * interaction with the input element.
21580 <example name="email-input-directive" module="emailExample">
21581 <file name="index.html">
21583 angular.module('emailExample', [])
21584 .controller('ExampleController', ['$scope', function($scope) {
21586 text: 'me@example.com'
21590 <form name="myForm" ng-controller="ExampleController">
21592 <input type="email" name="input" ng-model="email.text" required>
21595 <span class="error" ng-show="myForm.input.$error.required">
21597 <span class="error" ng-show="myForm.input.$error.email">
21598 Not valid email!</span>
21600 <tt>text = {{email.text}}</tt><br/>
21601 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21602 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21603 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21604 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21605 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21608 <file name="protractor.js" type="protractor">
21609 var text = element(by.binding('email.text'));
21610 var valid = element(by.binding('myForm.input.$valid'));
21611 var input = element(by.model('email.text'));
21613 it('should initialize to model', function() {
21614 expect(text.getText()).toContain('me@example.com');
21615 expect(valid.getText()).toContain('true');
21618 it('should be invalid if empty', function() {
21620 input.sendKeys('');
21621 expect(text.getText()).toEqual('text =');
21622 expect(valid.getText()).toContain('false');
21625 it('should be invalid if not email', function() {
21627 input.sendKeys('xxx');
21629 expect(valid.getText()).toContain('false');
21634 'email': emailInputType,
21639 * @name input[radio]
21642 * HTML radio button.
21644 * @param {string} ngModel Assignable angular expression to data-bind to.
21645 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21646 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21647 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21648 * @param {string=} name Property name of the form under which the control is published.
21649 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21650 * interaction with the input element.
21651 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21652 * is selected. Should be used instead of the `value` attribute if you need
21653 * a non-string `ngModel` (`boolean`, `array`, ...).
21656 <example name="radio-input-directive" module="radioExample">
21657 <file name="index.html">
21659 angular.module('radioExample', [])
21660 .controller('ExampleController', ['$scope', function($scope) {
21664 $scope.specialValue = {
21670 <form name="myForm" ng-controller="ExampleController">
21672 <input type="radio" ng-model="color.name" value="red">
21676 <input type="radio" ng-model="color.name" ng-value="specialValue">
21680 <input type="radio" ng-model="color.name" value="blue">
21683 <tt>color = {{color.name | json}}</tt><br/>
21685 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21687 <file name="protractor.js" type="protractor">
21688 it('should change state', function() {
21689 var color = element(by.binding('color.name'));
21691 expect(color.getText()).toContain('blue');
21693 element.all(by.model('color.name')).get(0).click();
21695 expect(color.getText()).toContain('red');
21700 'radio': radioInputType,
21705 * @name input[checkbox]
21710 * @param {string} ngModel Assignable angular expression to data-bind to.
21711 * @param {string=} name Property name of the form under which the control is published.
21712 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21713 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21714 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21715 * interaction with the input element.
21718 <example name="checkbox-input-directive" module="checkboxExample">
21719 <file name="index.html">
21721 angular.module('checkboxExample', [])
21722 .controller('ExampleController', ['$scope', function($scope) {
21723 $scope.checkboxModel = {
21729 <form name="myForm" ng-controller="ExampleController">
21731 <input type="checkbox" ng-model="checkboxModel.value1">
21734 <input type="checkbox" ng-model="checkboxModel.value2"
21735 ng-true-value="'YES'" ng-false-value="'NO'">
21737 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21738 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21741 <file name="protractor.js" type="protractor">
21742 it('should change state', function() {
21743 var value1 = element(by.binding('checkboxModel.value1'));
21744 var value2 = element(by.binding('checkboxModel.value2'));
21746 expect(value1.getText()).toContain('true');
21747 expect(value2.getText()).toContain('YES');
21749 element(by.model('checkboxModel.value1')).click();
21750 element(by.model('checkboxModel.value2')).click();
21752 expect(value1.getText()).toContain('false');
21753 expect(value2.getText()).toContain('NO');
21758 'checkbox': checkboxInputType,
21767 function stringBasedInputType(ctrl) {
21768 ctrl.$formatters.push(function(value) {
21769 return ctrl.$isEmpty(value) ? value : value.toString();
21773 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21774 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21775 stringBasedInputType(ctrl);
21778 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21779 var type = lowercase(element[0].type);
21781 // In composition mode, users are still inputing intermediate text buffer,
21782 // hold the listener until composition is done.
21783 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21784 if (!$sniffer.android) {
21785 var composing = false;
21787 element.on('compositionstart', function(data) {
21791 element.on('compositionend', function() {
21797 var listener = function(ev) {
21799 $browser.defer.cancel(timeout);
21802 if (composing) return;
21803 var value = element.val(),
21804 event = ev && ev.type;
21806 // By default we will trim the value
21807 // If the attribute ng-trim exists we will avoid trimming
21808 // If input type is 'password', the value is never trimmed
21809 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21810 value = trim(value);
21813 // If a control is suffering from bad input (due to native validators), browsers discard its
21814 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21815 // control's value is the same empty value twice in a row.
21816 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21817 ctrl.$setViewValue(value, event);
21821 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21822 // input event on backspace, delete or cut
21823 if ($sniffer.hasEvent('input')) {
21824 element.on('input', listener);
21828 var deferListener = function(ev, input, origValue) {
21830 timeout = $browser.defer(function() {
21832 if (!input || input.value !== origValue) {
21839 element.on('keydown', function(event) {
21840 var key = event.keyCode;
21843 // command modifiers arrows
21844 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21846 deferListener(event, this, this.value);
21849 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21850 if ($sniffer.hasEvent('paste')) {
21851 element.on('paste cut', deferListener);
21855 // if user paste into input using mouse on older browser
21856 // or form autocomplete on newer browser, we need "change" event to catch it
21857 element.on('change', listener);
21859 ctrl.$render = function() {
21860 // Workaround for Firefox validation #12102.
21861 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21862 if (element.val() !== value) {
21863 element.val(value);
21868 function weekParser(isoWeek, existingDate) {
21869 if (isDate(isoWeek)) {
21873 if (isString(isoWeek)) {
21874 WEEK_REGEXP.lastIndex = 0;
21875 var parts = WEEK_REGEXP.exec(isoWeek);
21877 var year = +parts[1],
21883 firstThurs = getFirstThursdayOfYear(year),
21884 addDays = (week - 1) * 7;
21886 if (existingDate) {
21887 hours = existingDate.getHours();
21888 minutes = existingDate.getMinutes();
21889 seconds = existingDate.getSeconds();
21890 milliseconds = existingDate.getMilliseconds();
21893 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21900 function createDateParser(regexp, mapping) {
21901 return function(iso, date) {
21908 if (isString(iso)) {
21909 // When a date is JSON'ified to wraps itself inside of an extra
21910 // set of double quotes. This makes the date parsing code unable
21911 // to match the date string and parse it as a date.
21912 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21913 iso = iso.substring(1, iso.length - 1);
21915 if (ISO_DATE_REGEXP.test(iso)) {
21916 return new Date(iso);
21918 regexp.lastIndex = 0;
21919 parts = regexp.exec(iso);
21925 yyyy: date.getFullYear(),
21926 MM: date.getMonth() + 1,
21927 dd: date.getDate(),
21928 HH: date.getHours(),
21929 mm: date.getMinutes(),
21930 ss: date.getSeconds(),
21931 sss: date.getMilliseconds() / 1000
21934 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21937 forEach(parts, function(part, index) {
21938 if (index < mapping.length) {
21939 map[mapping[index]] = +part;
21942 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21950 function createDateInputType(type, regexp, parseDate, format) {
21951 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21952 badInputChecker(scope, element, attr, ctrl);
21953 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21954 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21957 ctrl.$$parserName = type;
21958 ctrl.$parsers.push(function(value) {
21959 if (ctrl.$isEmpty(value)) return null;
21960 if (regexp.test(value)) {
21961 // Note: We cannot read ctrl.$modelValue, as there might be a different
21962 // parser/formatter in the processing chain so that the model
21963 // contains some different data format!
21964 var parsedDate = parseDate(value, previousDate);
21966 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21973 ctrl.$formatters.push(function(value) {
21974 if (value && !isDate(value)) {
21975 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21977 if (isValidDate(value)) {
21978 previousDate = value;
21979 if (previousDate && timezone) {
21980 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21982 return $filter('date')(value, format, timezone);
21984 previousDate = null;
21989 if (isDefined(attr.min) || attr.ngMin) {
21991 ctrl.$validators.min = function(value) {
21992 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21994 attr.$observe('min', function(val) {
21995 minVal = parseObservedDateValue(val);
22000 if (isDefined(attr.max) || attr.ngMax) {
22002 ctrl.$validators.max = function(value) {
22003 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
22005 attr.$observe('max', function(val) {
22006 maxVal = parseObservedDateValue(val);
22011 function isValidDate(value) {
22012 // Invalid Date: getTime() returns NaN
22013 return value && !(value.getTime && value.getTime() !== value.getTime());
22016 function parseObservedDateValue(val) {
22017 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
22022 function badInputChecker(scope, element, attr, ctrl) {
22023 var node = element[0];
22024 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
22025 if (nativeValidation) {
22026 ctrl.$parsers.push(function(value) {
22027 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
22028 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
22029 // - also sets validity.badInput (should only be validity.typeMismatch).
22030 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
22031 // - can ignore this case as we can still read out the erroneous email...
22032 return validity.badInput && !validity.typeMismatch ? undefined : value;
22037 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22038 badInputChecker(scope, element, attr, ctrl);
22039 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22041 ctrl.$$parserName = 'number';
22042 ctrl.$parsers.push(function(value) {
22043 if (ctrl.$isEmpty(value)) return null;
22044 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
22048 ctrl.$formatters.push(function(value) {
22049 if (!ctrl.$isEmpty(value)) {
22050 if (!isNumber(value)) {
22051 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
22053 value = value.toString();
22058 if (isDefined(attr.min) || attr.ngMin) {
22060 ctrl.$validators.min = function(value) {
22061 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
22064 attr.$observe('min', function(val) {
22065 if (isDefined(val) && !isNumber(val)) {
22066 val = parseFloat(val, 10);
22068 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
22069 // TODO(matsko): implement validateLater to reduce number of validations
22074 if (isDefined(attr.max) || attr.ngMax) {
22076 ctrl.$validators.max = function(value) {
22077 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22080 attr.$observe('max', function(val) {
22081 if (isDefined(val) && !isNumber(val)) {
22082 val = parseFloat(val, 10);
22084 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22085 // TODO(matsko): implement validateLater to reduce number of validations
22091 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22092 // Note: no badInputChecker here by purpose as `url` is only a validation
22093 // in browsers, i.e. we can always read out input.value even if it is not valid!
22094 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22095 stringBasedInputType(ctrl);
22097 ctrl.$$parserName = 'url';
22098 ctrl.$validators.url = function(modelValue, viewValue) {
22099 var value = modelValue || viewValue;
22100 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22104 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22105 // Note: no badInputChecker here by purpose as `url` is only a validation
22106 // in browsers, i.e. we can always read out input.value even if it is not valid!
22107 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22108 stringBasedInputType(ctrl);
22110 ctrl.$$parserName = 'email';
22111 ctrl.$validators.email = function(modelValue, viewValue) {
22112 var value = modelValue || viewValue;
22113 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22117 function radioInputType(scope, element, attr, ctrl) {
22118 // make the name unique, if not defined
22119 if (isUndefined(attr.name)) {
22120 element.attr('name', nextUid());
22123 var listener = function(ev) {
22124 if (element[0].checked) {
22125 ctrl.$setViewValue(attr.value, ev && ev.type);
22129 element.on('click', listener);
22131 ctrl.$render = function() {
22132 var value = attr.value;
22133 element[0].checked = (value == ctrl.$viewValue);
22136 attr.$observe('value', ctrl.$render);
22139 function parseConstantExpr($parse, context, name, expression, fallback) {
22141 if (isDefined(expression)) {
22142 parseFn = $parse(expression);
22143 if (!parseFn.constant) {
22144 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22145 '`{1}`.', name, expression);
22147 return parseFn(context);
22152 function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22153 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22154 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22156 var listener = function(ev) {
22157 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22160 element.on('click', listener);
22162 ctrl.$render = function() {
22163 element[0].checked = ctrl.$viewValue;
22166 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22167 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22168 // it to a boolean.
22169 ctrl.$isEmpty = function(value) {
22170 return value === false;
22173 ctrl.$formatters.push(function(value) {
22174 return equals(value, trueValue);
22177 ctrl.$parsers.push(function(value) {
22178 return value ? trueValue : falseValue;
22189 * HTML textarea element control with angular data-binding. The data-binding and validation
22190 * properties of this element are exactly the same as those of the
22191 * {@link ng.directive:input input element}.
22193 * @param {string} ngModel Assignable angular expression to data-bind to.
22194 * @param {string=} name Property name of the form under which the control is published.
22195 * @param {string=} required Sets `required` validation error key if the value is not entered.
22196 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22197 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22198 * `required` when you want to data-bind to the `required` attribute.
22199 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22201 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22202 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22204 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22205 * a RegExp found by evaluating the Angular expression given in the attribute value.
22206 * If the expression evaluates to a RegExp object, then this is used directly.
22207 * If the expression evaluates to a string, then it will be converted to a RegExp
22208 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22209 * `new RegExp('^abc$')`.<br />
22210 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22211 * start at the index of the last search's match, thus not taking the whole input value into
22213 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22214 * interaction with the input element.
22215 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22225 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22226 * input state control, and validation.
22227 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22229 * <div class="alert alert-warning">
22230 * **Note:** Not every feature offered is available for all input types.
22231 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22234 * @param {string} ngModel Assignable angular expression to data-bind to.
22235 * @param {string=} name Property name of the form under which the control is published.
22236 * @param {string=} required Sets `required` validation error key if the value is not entered.
22237 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22238 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22240 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22241 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22243 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22244 * a RegExp found by evaluating the Angular expression given in the attribute value.
22245 * If the expression evaluates to a RegExp object, then this is used directly.
22246 * If the expression evaluates to a string, then it will be converted to a RegExp
22247 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22248 * `new RegExp('^abc$')`.<br />
22249 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22250 * start at the index of the last search's match, thus not taking the whole input value into
22252 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22253 * interaction with the input element.
22254 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22255 * This parameter is ignored for input[type=password] controls, which will never trim the
22259 <example name="input-directive" module="inputExample">
22260 <file name="index.html">
22262 angular.module('inputExample', [])
22263 .controller('ExampleController', ['$scope', function($scope) {
22264 $scope.user = {name: 'guest', last: 'visitor'};
22267 <div ng-controller="ExampleController">
22268 <form name="myForm">
22271 <input type="text" name="userName" ng-model="user.name" required>
22274 <span class="error" ng-show="myForm.userName.$error.required">
22279 <input type="text" name="lastName" ng-model="user.last"
22280 ng-minlength="3" ng-maxlength="10">
22283 <span class="error" ng-show="myForm.lastName.$error.minlength">
22285 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22290 <tt>user = {{user}}</tt><br/>
22291 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22292 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22293 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22294 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22295 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22296 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22297 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22298 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22301 <file name="protractor.js" type="protractor">
22302 var user = element(by.exactBinding('user'));
22303 var userNameValid = element(by.binding('myForm.userName.$valid'));
22304 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22305 var lastNameError = element(by.binding('myForm.lastName.$error'));
22306 var formValid = element(by.binding('myForm.$valid'));
22307 var userNameInput = element(by.model('user.name'));
22308 var userLastInput = element(by.model('user.last'));
22310 it('should initialize to model', function() {
22311 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22312 expect(userNameValid.getText()).toContain('true');
22313 expect(formValid.getText()).toContain('true');
22316 it('should be invalid if empty when required', function() {
22317 userNameInput.clear();
22318 userNameInput.sendKeys('');
22320 expect(user.getText()).toContain('{"last":"visitor"}');
22321 expect(userNameValid.getText()).toContain('false');
22322 expect(formValid.getText()).toContain('false');
22325 it('should be valid if empty when min length is set', function() {
22326 userLastInput.clear();
22327 userLastInput.sendKeys('');
22329 expect(user.getText()).toContain('{"name":"guest","last":""}');
22330 expect(lastNameValid.getText()).toContain('true');
22331 expect(formValid.getText()).toContain('true');
22334 it('should be invalid if less than required min length', function() {
22335 userLastInput.clear();
22336 userLastInput.sendKeys('xx');
22338 expect(user.getText()).toContain('{"name":"guest"}');
22339 expect(lastNameValid.getText()).toContain('false');
22340 expect(lastNameError.getText()).toContain('minlength');
22341 expect(formValid.getText()).toContain('false');
22344 it('should be invalid if longer than max length', function() {
22345 userLastInput.clear();
22346 userLastInput.sendKeys('some ridiculously long name');
22348 expect(user.getText()).toContain('{"name":"guest"}');
22349 expect(lastNameValid.getText()).toContain('false');
22350 expect(lastNameError.getText()).toContain('maxlength');
22351 expect(formValid.getText()).toContain('false');
22356 var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22357 function($browser, $sniffer, $filter, $parse) {
22360 require: ['?ngModel'],
22362 pre: function(scope, element, attr, ctrls) {
22364 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22365 $browser, $filter, $parse);
22374 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22380 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22381 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22384 * `ngValue` is useful when dynamically generating lists of radio buttons using
22385 * {@link ngRepeat `ngRepeat`}, as shown below.
22387 * Likewise, `ngValue` can be used to generate `<option>` elements for
22388 * the {@link select `select`} element. In that case however, only strings are supported
22389 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22390 * Support for `select` models with non-string values is available via `ngOptions`.
22393 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22394 * of the `input` element
22397 <example name="ngValue-directive" module="valueExample">
22398 <file name="index.html">
22400 angular.module('valueExample', [])
22401 .controller('ExampleController', ['$scope', function($scope) {
22402 $scope.names = ['pizza', 'unicorns', 'robots'];
22403 $scope.my = { favorite: 'unicorns' };
22406 <form ng-controller="ExampleController">
22407 <h2>Which is your favorite?</h2>
22408 <label ng-repeat="name in names" for="{{name}}">
22410 <input type="radio"
22411 ng-model="my.favorite"
22416 <div>You chose {{my.favorite}}</div>
22419 <file name="protractor.js" type="protractor">
22420 var favorite = element(by.binding('my.favorite'));
22422 it('should initialize to model', function() {
22423 expect(favorite.getText()).toContain('unicorns');
22425 it('should bind the values to the inputs', function() {
22426 element.all(by.model('my.favorite')).get(0).click();
22427 expect(favorite.getText()).toContain('pizza');
22432 var ngValueDirective = function() {
22436 compile: function(tpl, tplAttr) {
22437 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22438 return function ngValueConstantLink(scope, elm, attr) {
22439 attr.$set('value', scope.$eval(attr.ngValue));
22442 return function ngValueLink(scope, elm, attr) {
22443 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22444 attr.$set('value', value);
22458 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22459 * with the value of a given expression, and to update the text content when the value of that
22460 * expression changes.
22462 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22463 * `{{ expression }}` which is similar but less verbose.
22465 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22466 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22467 * element attribute, it makes the bindings invisible to the user while the page is loading.
22469 * An alternative solution to this problem would be using the
22470 * {@link ng.directive:ngCloak ngCloak} directive.
22474 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22477 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22478 <example module="bindExample">
22479 <file name="index.html">
22481 angular.module('bindExample', [])
22482 .controller('ExampleController', ['$scope', function($scope) {
22483 $scope.name = 'Whirled';
22486 <div ng-controller="ExampleController">
22487 <label>Enter name: <input type="text" ng-model="name"></label><br>
22488 Hello <span ng-bind="name"></span>!
22491 <file name="protractor.js" type="protractor">
22492 it('should check ng-bind', function() {
22493 var nameInput = element(by.model('name'));
22495 expect(element(by.binding('name')).getText()).toBe('Whirled');
22497 nameInput.sendKeys('world');
22498 expect(element(by.binding('name')).getText()).toBe('world');
22503 var ngBindDirective = ['$compile', function($compile) {
22506 compile: function ngBindCompile(templateElement) {
22507 $compile.$$addBindingClass(templateElement);
22508 return function ngBindLink(scope, element, attr) {
22509 $compile.$$addBindingInfo(element, attr.ngBind);
22510 element = element[0];
22511 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22512 element.textContent = isUndefined(value) ? '' : value;
22522 * @name ngBindTemplate
22525 * The `ngBindTemplate` directive specifies that the element
22526 * text content should be replaced with the interpolation of the template
22527 * in the `ngBindTemplate` attribute.
22528 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22529 * expressions. This directive is needed since some HTML elements
22530 * (such as TITLE and OPTION) cannot contain SPAN elements.
22533 * @param {string} ngBindTemplate template of form
22534 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22537 * Try it here: enter text in text box and watch the greeting change.
22538 <example module="bindExample">
22539 <file name="index.html">
22541 angular.module('bindExample', [])
22542 .controller('ExampleController', ['$scope', function($scope) {
22543 $scope.salutation = 'Hello';
22544 $scope.name = 'World';
22547 <div ng-controller="ExampleController">
22548 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22549 <label>Name: <input type="text" ng-model="name"></label><br>
22550 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22553 <file name="protractor.js" type="protractor">
22554 it('should check ng-bind', function() {
22555 var salutationElem = element(by.binding('salutation'));
22556 var salutationInput = element(by.model('salutation'));
22557 var nameInput = element(by.model('name'));
22559 expect(salutationElem.getText()).toBe('Hello World!');
22561 salutationInput.clear();
22562 salutationInput.sendKeys('Greetings');
22564 nameInput.sendKeys('user');
22566 expect(salutationElem.getText()).toBe('Greetings user!');
22571 var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22573 compile: function ngBindTemplateCompile(templateElement) {
22574 $compile.$$addBindingClass(templateElement);
22575 return function ngBindTemplateLink(scope, element, attr) {
22576 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22577 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22578 element = element[0];
22579 attr.$observe('ngBindTemplate', function(value) {
22580 element.textContent = isUndefined(value) ? '' : value;
22593 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22594 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22595 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22596 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22597 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22599 * You may also bypass sanitization for values you know are safe. To do so, bind to
22600 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22601 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22603 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22604 * will have an exception (instead of an exploit.)
22607 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22611 <example module="bindHtmlExample" deps="angular-sanitize.js">
22612 <file name="index.html">
22613 <div ng-controller="ExampleController">
22614 <p ng-bind-html="myHTML"></p>
22618 <file name="script.js">
22619 angular.module('bindHtmlExample', ['ngSanitize'])
22620 .controller('ExampleController', ['$scope', function($scope) {
22622 'I am an <code>HTML</code>string with ' +
22623 '<a href="#">links!</a> and other <em>stuff</em>';
22627 <file name="protractor.js" type="protractor">
22628 it('should check ng-bind-html', function() {
22629 expect(element(by.binding('myHTML')).getText()).toBe(
22630 'I am an HTMLstring with links! and other stuff');
22635 var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22638 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22639 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22640 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22641 return (value || '').toString();
22643 $compile.$$addBindingClass(tElement);
22645 return function ngBindHtmlLink(scope, element, attr) {
22646 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22648 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22649 // we re-evaluate the expr because we want a TrustedValueHolderType
22650 // for $sce, not a string
22651 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22663 * Evaluate the given expression when the user changes the input.
22664 * The expression is evaluated immediately, unlike the JavaScript onchange event
22665 * which only triggers at the end of a change (usually, when the user leaves the
22666 * form element or presses the return key).
22668 * The `ngChange` expression is only evaluated when a change in the input value causes
22669 * a new value to be committed to the model.
22671 * It will not be evaluated:
22672 * * if the value returned from the `$parsers` transformation pipeline has not changed
22673 * * if the input has continued to be invalid since the model will stay `null`
22674 * * if the model is changed programmatically and not by a change to the input value
22677 * Note, this directive requires `ngModel` to be present.
22680 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22684 * <example name="ngChange-directive" module="changeExample">
22685 * <file name="index.html">
22687 * angular.module('changeExample', [])
22688 * .controller('ExampleController', ['$scope', function($scope) {
22689 * $scope.counter = 0;
22690 * $scope.change = function() {
22691 * $scope.counter++;
22695 * <div ng-controller="ExampleController">
22696 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22697 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22698 * <label for="ng-change-example2">Confirmed</label><br />
22699 * <tt>debug = {{confirmed}}</tt><br/>
22700 * <tt>counter = {{counter}}</tt><br/>
22703 * <file name="protractor.js" type="protractor">
22704 * var counter = element(by.binding('counter'));
22705 * var debug = element(by.binding('confirmed'));
22707 * it('should evaluate the expression if changing from view', function() {
22708 * expect(counter.getText()).toContain('0');
22710 * element(by.id('ng-change-example1')).click();
22712 * expect(counter.getText()).toContain('1');
22713 * expect(debug.getText()).toContain('true');
22716 * it('should not evaluate the expression if changing from model', function() {
22717 * element(by.id('ng-change-example2')).click();
22719 * expect(counter.getText()).toContain('0');
22720 * expect(debug.getText()).toContain('true');
22725 var ngChangeDirective = valueFn({
22727 require: 'ngModel',
22728 link: function(scope, element, attr, ctrl) {
22729 ctrl.$viewChangeListeners.push(function() {
22730 scope.$eval(attr.ngChange);
22735 function classDirective(name, selector) {
22736 name = 'ngClass' + name;
22737 return ['$animate', function($animate) {
22740 link: function(scope, element, attr) {
22743 scope.$watch(attr[name], ngClassWatchAction, true);
22745 attr.$observe('class', function(value) {
22746 ngClassWatchAction(scope.$eval(attr[name]));
22750 if (name !== 'ngClass') {
22751 scope.$watch('$index', function($index, old$index) {
22752 // jshint bitwise: false
22753 var mod = $index & 1;
22754 if (mod !== (old$index & 1)) {
22755 var classes = arrayClasses(scope.$eval(attr[name]));
22757 addClasses(classes) :
22758 removeClasses(classes);
22763 function addClasses(classes) {
22764 var newClasses = digestClassCounts(classes, 1);
22765 attr.$addClass(newClasses);
22768 function removeClasses(classes) {
22769 var newClasses = digestClassCounts(classes, -1);
22770 attr.$removeClass(newClasses);
22773 function digestClassCounts(classes, count) {
22774 // Use createMap() to prevent class assumptions involving property
22775 // names in Object.prototype
22776 var classCounts = element.data('$classCounts') || createMap();
22777 var classesToUpdate = [];
22778 forEach(classes, function(className) {
22779 if (count > 0 || classCounts[className]) {
22780 classCounts[className] = (classCounts[className] || 0) + count;
22781 if (classCounts[className] === +(count > 0)) {
22782 classesToUpdate.push(className);
22786 element.data('$classCounts', classCounts);
22787 return classesToUpdate.join(' ');
22790 function updateClasses(oldClasses, newClasses) {
22791 var toAdd = arrayDifference(newClasses, oldClasses);
22792 var toRemove = arrayDifference(oldClasses, newClasses);
22793 toAdd = digestClassCounts(toAdd, 1);
22794 toRemove = digestClassCounts(toRemove, -1);
22795 if (toAdd && toAdd.length) {
22796 $animate.addClass(element, toAdd);
22798 if (toRemove && toRemove.length) {
22799 $animate.removeClass(element, toRemove);
22803 function ngClassWatchAction(newVal) {
22804 if (selector === true || scope.$index % 2 === selector) {
22805 var newClasses = arrayClasses(newVal || []);
22807 addClasses(newClasses);
22808 } else if (!equals(newVal,oldVal)) {
22809 var oldClasses = arrayClasses(oldVal);
22810 updateClasses(oldClasses, newClasses);
22813 oldVal = shallowCopy(newVal);
22818 function arrayDifference(tokens1, tokens2) {
22822 for (var i = 0; i < tokens1.length; i++) {
22823 var token = tokens1[i];
22824 for (var j = 0; j < tokens2.length; j++) {
22825 if (token == tokens2[j]) continue outer;
22827 values.push(token);
22832 function arrayClasses(classVal) {
22834 if (isArray(classVal)) {
22835 forEach(classVal, function(v) {
22836 classes = classes.concat(arrayClasses(v));
22839 } else if (isString(classVal)) {
22840 return classVal.split(' ');
22841 } else if (isObject(classVal)) {
22842 forEach(classVal, function(v, k) {
22844 classes = classes.concat(k.split(' '));
22860 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22861 * an expression that represents all classes to be added.
22863 * The directive operates in three different ways, depending on which of three types the expression
22866 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22869 * 2. If the expression evaluates to an object, then for each key-value pair of the
22870 * object with a truthy value the corresponding key is used as a class name.
22872 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22873 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22874 * to give you more control over what CSS classes appear. See the code below for an example of this.
22877 * The directive won't add duplicate classes if a particular class was already set.
22879 * When the expression changes, the previously added classes are removed and only then are the
22880 * new classes added.
22883 * **add** - happens just before the class is applied to the elements
22885 * **remove** - happens just before the class is removed from the element
22888 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22889 * of the evaluation can be a string representing space delimited class
22890 * names, an array, or a map of class names to boolean values. In the case of a map, the
22891 * names of the properties whose values are truthy will be added as css classes to the
22894 * @example Example that demonstrates basic bindings via ngClass directive.
22896 <file name="index.html">
22897 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22899 <input type="checkbox" ng-model="deleted">
22900 deleted (apply "strike" class)
22903 <input type="checkbox" ng-model="important">
22904 important (apply "bold" class)
22907 <input type="checkbox" ng-model="error">
22908 error (apply "has-error" class)
22911 <p ng-class="style">Using String Syntax</p>
22912 <input type="text" ng-model="style"
22913 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22915 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22916 <input ng-model="style1"
22917 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22918 <input ng-model="style2"
22919 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22920 <input ng-model="style3"
22921 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22923 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22924 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22925 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22927 <file name="style.css">
22929 text-decoration: line-through;
22939 background-color: yellow;
22945 <file name="protractor.js" type="protractor">
22946 var ps = element.all(by.css('p'));
22948 it('should let you toggle the class', function() {
22950 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22951 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22953 element(by.model('important')).click();
22954 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22956 element(by.model('error')).click();
22957 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22960 it('should let you toggle string example', function() {
22961 expect(ps.get(1).getAttribute('class')).toBe('');
22962 element(by.model('style')).clear();
22963 element(by.model('style')).sendKeys('red');
22964 expect(ps.get(1).getAttribute('class')).toBe('red');
22967 it('array example should have 3 classes', function() {
22968 expect(ps.get(2).getAttribute('class')).toBe('');
22969 element(by.model('style1')).sendKeys('bold');
22970 element(by.model('style2')).sendKeys('strike');
22971 element(by.model('style3')).sendKeys('red');
22972 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22975 it('array with map example should have 2 classes', function() {
22976 expect(ps.last().getAttribute('class')).toBe('');
22977 element(by.model('style4')).sendKeys('bold');
22978 element(by.model('warning')).click();
22979 expect(ps.last().getAttribute('class')).toBe('bold orange');
22986 The example below demonstrates how to perform animations using ngClass.
22988 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22989 <file name="index.html">
22990 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22991 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22993 <span class="base-class" ng-class="myVar">Sample Text</span>
22995 <file name="style.css">
22997 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
23000 .base-class.my-class {
23005 <file name="protractor.js" type="protractor">
23006 it('should check ng-class', function() {
23007 expect(element(by.css('.base-class')).getAttribute('class')).not.
23008 toMatch(/my-class/);
23010 element(by.id('setbtn')).click();
23012 expect(element(by.css('.base-class')).getAttribute('class')).
23013 toMatch(/my-class/);
23015 element(by.id('clearbtn')).click();
23017 expect(element(by.css('.base-class')).getAttribute('class')).not.
23018 toMatch(/my-class/);
23024 ## ngClass and pre-existing CSS3 Transitions/Animations
23025 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
23026 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
23027 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
23028 to view the step by step details of {@link $animate#addClass $animate.addClass} and
23029 {@link $animate#removeClass $animate.removeClass}.
23031 var ngClassDirective = classDirective('', true);
23039 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23040 * {@link ng.directive:ngClass ngClass}, except they work in
23041 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23043 * This directive can be applied only within the scope of an
23044 * {@link ng.directive:ngRepeat ngRepeat}.
23047 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
23048 * of the evaluation can be a string representing space delimited class names or an array.
23052 <file name="index.html">
23053 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23054 <li ng-repeat="name in names">
23055 <span ng-class-odd="'odd'" ng-class-even="'even'">
23061 <file name="style.css">
23069 <file name="protractor.js" type="protractor">
23070 it('should check ng-class-odd and ng-class-even', function() {
23071 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23073 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23079 var ngClassOddDirective = classDirective('Odd', 0);
23083 * @name ngClassEven
23087 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23088 * {@link ng.directive:ngClass ngClass}, except they work in
23089 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23091 * This directive can be applied only within the scope of an
23092 * {@link ng.directive:ngRepeat ngRepeat}.
23095 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23096 * result of the evaluation can be a string representing space delimited class names or an array.
23100 <file name="index.html">
23101 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23102 <li ng-repeat="name in names">
23103 <span ng-class-odd="'odd'" ng-class-even="'even'">
23104 {{name}}
23109 <file name="style.css">
23117 <file name="protractor.js" type="protractor">
23118 it('should check ng-class-odd and ng-class-even', function() {
23119 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23121 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23127 var ngClassEvenDirective = classDirective('Even', 1);
23135 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23136 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23137 * directive to avoid the undesirable flicker effect caused by the html template display.
23139 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23140 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23141 * of the browser view.
23143 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23144 * `angular.min.js`.
23145 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23148 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23149 * display: none !important;
23153 * When this css rule is loaded by the browser, all html elements (including their children) that
23154 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23155 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23156 * the compiled element visible.
23158 * For the best result, the `angular.js` script must be loaded in the head section of the html
23159 * document; alternatively, the css rule above must be included in the external stylesheet of the
23166 <file name="index.html">
23167 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23168 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23170 <file name="protractor.js" type="protractor">
23171 it('should remove the template directive and css class', function() {
23172 expect($('#template1').getAttribute('ng-cloak')).
23174 expect($('#template2').getAttribute('ng-cloak')).
23181 var ngCloakDirective = ngDirective({
23182 compile: function(element, attr) {
23183 attr.$set('ngCloak', undefined);
23184 element.removeClass('ng-cloak');
23190 * @name ngController
23193 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23194 * supports the principles behind the Model-View-Controller design pattern.
23196 * MVC components in angular:
23198 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23199 * are accessed through bindings.
23200 * * View — The template (HTML with data bindings) that is rendered into the View.
23201 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23202 * logic behind the application to decorate the scope with functions and values
23204 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23205 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23206 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23207 * and executed twice.
23212 * @param {expression} ngController Name of a constructor function registered with the current
23213 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23214 * that on the current scope evaluates to a constructor function.
23216 * The controller instance can be published into a scope property by specifying
23217 * `ng-controller="as propertyName"`.
23219 * If the current `$controllerProvider` is configured to use globals (via
23220 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23221 * also be the name of a globally accessible constructor function (not recommended).
23224 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23225 * greeting are methods declared on the controller (see source tab). These methods can
23226 * easily be called from the angular markup. Any changes to the data are automatically reflected
23227 * in the View without the need for a manual update.
23229 * Two different declaration styles are included below:
23231 * * one binds methods and properties directly onto the controller using `this`:
23232 * `ng-controller="SettingsController1 as settings"`
23233 * * one injects `$scope` into the controller:
23234 * `ng-controller="SettingsController2"`
23236 * The second option is more common in the Angular community, and is generally used in boilerplates
23237 * and in this guide. However, there are advantages to binding properties directly to the controller
23238 * and avoiding scope.
23240 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23241 * multiple controllers apply to an element.
23242 * * If you are writing your controllers as classes you have easier access to the properties and
23243 * methods, which will appear on the scope, from inside the controller code.
23244 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23245 * inheritance masking primitives.
23247 * This example demonstrates the `controller as` syntax.
23249 * <example name="ngControllerAs" module="controllerAsExample">
23250 * <file name="index.html">
23251 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23252 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23253 * <button ng-click="settings.greet()">greet</button><br/>
23256 * <li ng-repeat="contact in settings.contacts">
23257 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23258 * <option>phone</option>
23259 * <option>email</option>
23261 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23262 * <button ng-click="settings.clearContact(contact)">clear</button>
23263 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23265 * <li><button ng-click="settings.addContact()">add</button></li>
23269 * <file name="app.js">
23270 * angular.module('controllerAsExample', [])
23271 * .controller('SettingsController1', SettingsController1);
23273 * function SettingsController1() {
23274 * this.name = "John Smith";
23275 * this.contacts = [
23276 * {type: 'phone', value: '408 555 1212'},
23277 * {type: 'email', value: 'john.smith@example.org'} ];
23280 * SettingsController1.prototype.greet = function() {
23281 * alert(this.name);
23284 * SettingsController1.prototype.addContact = function() {
23285 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23288 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23289 * var index = this.contacts.indexOf(contactToRemove);
23290 * this.contacts.splice(index, 1);
23293 * SettingsController1.prototype.clearContact = function(contact) {
23294 * contact.type = 'phone';
23295 * contact.value = '';
23298 * <file name="protractor.js" type="protractor">
23299 * it('should check controller as', function() {
23300 * var container = element(by.id('ctrl-as-exmpl'));
23301 * expect(container.element(by.model('settings.name'))
23302 * .getAttribute('value')).toBe('John Smith');
23304 * var firstRepeat =
23305 * container.element(by.repeater('contact in settings.contacts').row(0));
23306 * var secondRepeat =
23307 * container.element(by.repeater('contact in settings.contacts').row(1));
23309 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23310 * .toBe('408 555 1212');
23312 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23313 * .toBe('john.smith@example.org');
23315 * firstRepeat.element(by.buttonText('clear')).click();
23317 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23320 * container.element(by.buttonText('add')).click();
23322 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23323 * .element(by.model('contact.value'))
23324 * .getAttribute('value'))
23325 * .toBe('yourname@example.org');
23330 * This example demonstrates the "attach to `$scope`" style of controller.
23332 * <example name="ngController" module="controllerExample">
23333 * <file name="index.html">
23334 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23335 * <label>Name: <input type="text" ng-model="name"/></label>
23336 * <button ng-click="greet()">greet</button><br/>
23339 * <li ng-repeat="contact in contacts">
23340 * <select ng-model="contact.type" id="select_{{$index}}">
23341 * <option>phone</option>
23342 * <option>email</option>
23344 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23345 * <button ng-click="clearContact(contact)">clear</button>
23346 * <button ng-click="removeContact(contact)">X</button>
23348 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23352 * <file name="app.js">
23353 * angular.module('controllerExample', [])
23354 * .controller('SettingsController2', ['$scope', SettingsController2]);
23356 * function SettingsController2($scope) {
23357 * $scope.name = "John Smith";
23358 * $scope.contacts = [
23359 * {type:'phone', value:'408 555 1212'},
23360 * {type:'email', value:'john.smith@example.org'} ];
23362 * $scope.greet = function() {
23363 * alert($scope.name);
23366 * $scope.addContact = function() {
23367 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23370 * $scope.removeContact = function(contactToRemove) {
23371 * var index = $scope.contacts.indexOf(contactToRemove);
23372 * $scope.contacts.splice(index, 1);
23375 * $scope.clearContact = function(contact) {
23376 * contact.type = 'phone';
23377 * contact.value = '';
23381 * <file name="protractor.js" type="protractor">
23382 * it('should check controller', function() {
23383 * var container = element(by.id('ctrl-exmpl'));
23385 * expect(container.element(by.model('name'))
23386 * .getAttribute('value')).toBe('John Smith');
23388 * var firstRepeat =
23389 * container.element(by.repeater('contact in contacts').row(0));
23390 * var secondRepeat =
23391 * container.element(by.repeater('contact in contacts').row(1));
23393 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23394 * .toBe('408 555 1212');
23395 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23396 * .toBe('john.smith@example.org');
23398 * firstRepeat.element(by.buttonText('clear')).click();
23400 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23403 * container.element(by.buttonText('add')).click();
23405 * expect(container.element(by.repeater('contact in contacts').row(2))
23406 * .element(by.model('contact.value'))
23407 * .getAttribute('value'))
23408 * .toBe('yourname@example.org');
23414 var ngControllerDirective = [function() {
23430 * Angular has some features that can break certain
23431 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23433 * If you intend to implement these rules then you must tell Angular not to use these features.
23435 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23438 * The following rules affect Angular:
23440 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23441 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23442 * increase in the speed of evaluating Angular expressions.
23444 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23445 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23446 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23447 * `angular-csp.css` in your HTML manually.
23449 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23450 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23451 * however, triggers a CSP error to be logged in the console:
23454 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23455 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23456 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23459 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23460 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23461 * the `angular.js` file.
23463 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23465 * You can specify which of the CSP related Angular features should be deactivated by providing
23466 * a value for the `ng-csp` attribute. The options are as follows:
23468 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23470 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23472 * You can use these values in the following combinations:
23475 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23476 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23479 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23480 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23483 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23484 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23486 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23487 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23489 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23490 * styles nor use eval, which is the same as an empty: ng-csp.
23491 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23494 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23497 <html ng-app ng-csp>
23503 // Note: the suffix `.csp` in the example name triggers
23504 // csp mode in our http server!
23505 <example name="example.csp" module="cspExample" ng-csp="true">
23506 <file name="index.html">
23507 <div ng-controller="MainController as ctrl">
23509 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23510 <span id="counter">
23516 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23517 <span id="evilError">
23523 <file name="script.js">
23524 angular.module('cspExample', [])
23525 .controller('MainController', function() {
23527 this.inc = function() {
23530 this.evil = function() {
23531 // jshint evil:true
23535 this.evilError = e.message;
23540 <file name="protractor.js" type="protractor">
23541 var util, webdriver;
23543 var incBtn = element(by.id('inc'));
23544 var counter = element(by.id('counter'));
23545 var evilBtn = element(by.id('evil'));
23546 var evilError = element(by.id('evilError'));
23548 function getAndClearSevereErrors() {
23549 return browser.manage().logs().get('browser').then(function(browserLog) {
23550 return browserLog.filter(function(logEntry) {
23551 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23556 function clearErrors() {
23557 getAndClearSevereErrors();
23560 function expectNoErrors() {
23561 getAndClearSevereErrors().then(function(filteredLog) {
23562 expect(filteredLog.length).toEqual(0);
23563 if (filteredLog.length) {
23564 console.log('browser console errors: ' + util.inspect(filteredLog));
23569 function expectError(regex) {
23570 getAndClearSevereErrors().then(function(filteredLog) {
23572 filteredLog.forEach(function(log) {
23573 if (log.message.match(regex)) {
23578 throw new Error('expected an error that matches ' + regex);
23583 beforeEach(function() {
23584 util = require('util');
23585 webdriver = require('protractor/node_modules/selenium-webdriver');
23588 // For now, we only test on Chrome,
23589 // as Safari does not load the page with Protractor's injected scripts,
23590 // and Firefox webdriver always disables content security policy (#6358)
23591 if (browser.params.browser !== 'chrome') {
23595 it('should not report errors when the page is loaded', function() {
23596 // clear errors so we are not dependent on previous tests
23598 // Need to reload the page as the page is already loaded when
23600 browser.driver.getCurrentUrl().then(function(url) {
23606 it('should evaluate expressions', function() {
23607 expect(counter.getText()).toEqual('0');
23609 expect(counter.getText()).toEqual('1');
23613 it('should throw and report an error when using "eval"', function() {
23615 expect(evilError.getText()).toMatch(/Content Security Policy/);
23616 expectError(/Content Security Policy/);
23622 // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23623 // bootstrap the system (before $parse is instantiated), for this reason we just have
23624 // the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23631 * The ngClick directive allows you to specify custom behavior when
23632 * an element is clicked.
23636 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23637 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23641 <file name="index.html">
23642 <button ng-click="count = count + 1" ng-init="count=0">
23649 <file name="protractor.js" type="protractor">
23650 it('should check ng-click', function() {
23651 expect(element(by.binding('count')).getText()).toMatch('0');
23652 element(by.css('button')).click();
23653 expect(element(by.binding('count')).getText()).toMatch('1');
23659 * A collection of directives that allows creation of custom event handlers that are defined as
23660 * angular expressions and are compiled and executed within the current scope.
23662 var ngEventDirectives = {};
23664 // For events that might fire synchronously during DOM manipulation
23665 // we need to execute their event handlers asynchronously using $evalAsync,
23666 // so that they are not executed in an inconsistent state.
23667 var forceAsyncEvents = {
23672 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23673 function(eventName) {
23674 var directiveName = directiveNormalize('ng-' + eventName);
23675 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23678 compile: function($element, attr) {
23679 // We expose the powerful $event object on the scope that provides access to the Window,
23680 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23681 // checks at the cost of speed since event handler expressions are not executed as
23682 // frequently as regular change detection.
23683 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23684 return function ngEventHandler(scope, element) {
23685 element.on(eventName, function(event) {
23686 var callback = function() {
23687 fn(scope, {$event:event});
23689 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23690 scope.$evalAsync(callback);
23692 scope.$apply(callback);
23707 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23711 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23712 * a dblclick. (The Event object is available as `$event`)
23716 <file name="index.html">
23717 <button ng-dblclick="count = count + 1" ng-init="count=0">
23718 Increment (on double click)
23728 * @name ngMousedown
23731 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23735 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23736 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23740 <file name="index.html">
23741 <button ng-mousedown="count = count + 1" ng-init="count=0">
23742 Increment (on mouse down)
23755 * Specify custom behavior on mouseup event.
23759 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23760 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23764 <file name="index.html">
23765 <button ng-mouseup="count = count + 1" ng-init="count=0">
23766 Increment (on mouse up)
23775 * @name ngMouseover
23778 * Specify custom behavior on mouseover event.
23782 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23783 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23787 <file name="index.html">
23788 <button ng-mouseover="count = count + 1" ng-init="count=0">
23789 Increment (when mouse is over)
23799 * @name ngMouseenter
23802 * Specify custom behavior on mouseenter event.
23806 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23807 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23811 <file name="index.html">
23812 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23813 Increment (when mouse enters)
23823 * @name ngMouseleave
23826 * Specify custom behavior on mouseleave event.
23830 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23831 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23835 <file name="index.html">
23836 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23837 Increment (when mouse leaves)
23847 * @name ngMousemove
23850 * Specify custom behavior on mousemove event.
23854 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23855 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23859 <file name="index.html">
23860 <button ng-mousemove="count = count + 1" ng-init="count=0">
23861 Increment (when mouse moves)
23874 * Specify custom behavior on keydown event.
23878 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23879 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23883 <file name="index.html">
23884 <input ng-keydown="count = count + 1" ng-init="count=0">
23885 key down count: {{count}}
23896 * Specify custom behavior on keyup event.
23900 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23901 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23905 <file name="index.html">
23906 <p>Typing in the input box below updates the key count</p>
23907 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23909 <p>Typing in the input box below updates the keycode</p>
23910 <input ng-keyup="event=$event">
23911 <p>event keyCode: {{ event.keyCode }}</p>
23912 <p>event altKey: {{ event.altKey }}</p>
23923 * Specify custom behavior on keypress event.
23926 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23927 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23928 * and can be interrogated for keyCode, altKey, etc.)
23932 <file name="index.html">
23933 <input ng-keypress="count = count + 1" ng-init="count=0">
23934 key press count: {{count}}
23945 * Enables binding angular expressions to onsubmit events.
23947 * Additionally it prevents the default action (which for form means sending the request to the
23948 * server and reloading the current page), but only if the form does not contain `action`,
23949 * `data-action`, or `x-action` attributes.
23951 * <div class="alert alert-warning">
23952 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23953 * `ngSubmit` handlers together. See the
23954 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23955 * for a detailed discussion of when `ngSubmit` may be triggered.
23960 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23961 * ({@link guide/expression#-event- Event object is available as `$event`})
23964 <example module="submitExample">
23965 <file name="index.html">
23967 angular.module('submitExample', [])
23968 .controller('ExampleController', ['$scope', function($scope) {
23970 $scope.text = 'hello';
23971 $scope.submit = function() {
23973 $scope.list.push(this.text);
23979 <form ng-submit="submit()" ng-controller="ExampleController">
23980 Enter text and hit enter:
23981 <input type="text" ng-model="text" name="text" />
23982 <input type="submit" id="submit" value="Submit" />
23983 <pre>list={{list}}</pre>
23986 <file name="protractor.js" type="protractor">
23987 it('should check ng-submit', function() {
23988 expect(element(by.binding('list')).getText()).toBe('list=[]');
23989 element(by.css('#submit')).click();
23990 expect(element(by.binding('list')).getText()).toContain('hello');
23991 expect(element(by.model('text')).getAttribute('value')).toBe('');
23993 it('should ignore empty strings', function() {
23994 expect(element(by.binding('list')).getText()).toBe('list=[]');
23995 element(by.css('#submit')).click();
23996 element(by.css('#submit')).click();
23997 expect(element(by.binding('list')).getText()).toContain('hello');
24008 * Specify custom behavior on focus event.
24010 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
24011 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24012 * during an `$apply` to ensure a consistent state.
24014 * @element window, input, select, textarea, a
24016 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
24017 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
24020 * See {@link ng.directive:ngClick ngClick}
24028 * Specify custom behavior on blur event.
24030 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
24031 * an element has lost focus.
24033 * Note: As the `blur` event is executed synchronously also during DOM manipulations
24034 * (e.g. removing a focussed input),
24035 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24036 * during an `$apply` to ensure a consistent state.
24038 * @element window, input, select, textarea, a
24040 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
24041 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
24044 * See {@link ng.directive:ngClick ngClick}
24052 * Specify custom behavior on copy event.
24054 * @element window, input, select, textarea, a
24056 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
24057 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
24061 <file name="index.html">
24062 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
24073 * Specify custom behavior on cut event.
24075 * @element window, input, select, textarea, a
24077 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24078 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24082 <file name="index.html">
24083 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24094 * Specify custom behavior on paste event.
24096 * @element window, input, select, textarea, a
24098 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24099 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24103 <file name="index.html">
24104 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24117 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24118 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24119 * value then the element is removed from the DOM, otherwise a clone of the
24120 * element is reinserted into the DOM.
24122 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24123 * element in the DOM rather than changing its visibility via the `display` css property. A common
24124 * case when this difference is significant is when using css selectors that rely on an element's
24125 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24127 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24128 * is created when the element is restored. The scope created within `ngIf` inherits from
24129 * its parent scope using
24130 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24131 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24132 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24133 * variable within the child scope will override (hide) the value in the parent scope.
24135 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24136 * is if an element's class attribute is directly modified after it's compiled, using something like
24137 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24138 * the added class will be lost because the original compiled state is used to regenerate the element.
24140 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24141 * and `leave` effects.
24144 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24145 * leave - happens just before the `ngIf` contents are removed from the DOM
24150 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24151 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24152 * element is added to the DOM tree.
24155 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24156 <file name="index.html">
24157 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24159 <span ng-if="checked" class="animate-if">
24160 This is removed when the checkbox is unchecked.
24163 <file name="animations.css">
24166 border:1px solid black;
24170 .animate-if.ng-enter, .animate-if.ng-leave {
24171 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24174 .animate-if.ng-enter,
24175 .animate-if.ng-leave.ng-leave-active {
24179 .animate-if.ng-leave,
24180 .animate-if.ng-enter.ng-enter-active {
24186 var ngIfDirective = ['$animate', function($animate) {
24188 multiElement: true,
24189 transclude: 'element',
24194 link: function($scope, $element, $attr, ctrl, $transclude) {
24195 var block, childScope, previousElements;
24196 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24200 $transclude(function(clone, newScope) {
24201 childScope = newScope;
24202 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24203 // Note: We only need the first/last node of the cloned nodes.
24204 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24205 // by a directive with templateUrl when its template arrives.
24209 $animate.enter(clone, $element.parent(), $element);
24213 if (previousElements) {
24214 previousElements.remove();
24215 previousElements = null;
24218 childScope.$destroy();
24222 previousElements = getBlockNodes(block.clone);
24223 $animate.leave(previousElements).then(function() {
24224 previousElements = null;
24240 * Fetches, compiles and includes an external HTML fragment.
24242 * By default, the template URL is restricted to the same domain and protocol as the
24243 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24244 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24245 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24246 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24247 * ng.$sce Strict Contextual Escaping}.
24249 * In addition, the browser's
24250 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24251 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24252 * policy may further restrict whether the template is successfully loaded.
24253 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24254 * access on some browsers.
24257 * enter - animation is used to bring new content into the browser.
24258 * leave - animation is used to animate existing content away.
24260 * The enter and leave animation occur concurrently.
24265 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24266 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24267 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24268 * <div class="alert alert-warning">
24269 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24270 * a function with the name on the window element, which will usually throw a
24271 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24272 * different form that {@link guide/directive#normalization matches} `onload`.
24275 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24276 * $anchorScroll} to scroll the viewport after the content is loaded.
24278 * - If the attribute is not set, disable scrolling.
24279 * - If the attribute is set without value, enable scrolling.
24280 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24283 <example module="includeExample" deps="angular-animate.js" animations="true">
24284 <file name="index.html">
24285 <div ng-controller="ExampleController">
24286 <select ng-model="template" ng-options="t.name for t in templates">
24287 <option value="">(blank)</option>
24289 url of the template: <code>{{template.url}}</code>
24291 <div class="slide-animate-container">
24292 <div class="slide-animate" ng-include="template.url"></div>
24296 <file name="script.js">
24297 angular.module('includeExample', ['ngAnimate'])
24298 .controller('ExampleController', ['$scope', function($scope) {
24300 [ { name: 'template1.html', url: 'template1.html'},
24301 { name: 'template2.html', url: 'template2.html'} ];
24302 $scope.template = $scope.templates[0];
24305 <file name="template1.html">
24306 Content of template1.html
24308 <file name="template2.html">
24309 Content of template2.html
24311 <file name="animations.css">
24312 .slide-animate-container {
24315 border:1px solid black;
24324 .slide-animate.ng-enter, .slide-animate.ng-leave {
24325 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24336 .slide-animate.ng-enter {
24339 .slide-animate.ng-enter.ng-enter-active {
24343 .slide-animate.ng-leave {
24346 .slide-animate.ng-leave.ng-leave-active {
24350 <file name="protractor.js" type="protractor">
24351 var templateSelect = element(by.model('template'));
24352 var includeElem = element(by.css('[ng-include]'));
24354 it('should load template1.html', function() {
24355 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24358 it('should load template2.html', function() {
24359 if (browser.params.browser == 'firefox') {
24360 // Firefox can't handle using selects
24361 // See https://github.com/angular/protractor/issues/480
24364 templateSelect.click();
24365 templateSelect.all(by.css('option')).get(2).click();
24366 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24369 it('should change to blank', function() {
24370 if (browser.params.browser == 'firefox') {
24371 // Firefox can't handle using selects
24374 templateSelect.click();
24375 templateSelect.all(by.css('option')).get(0).click();
24376 expect(includeElem.isPresent()).toBe(false);
24385 * @name ngInclude#$includeContentRequested
24386 * @eventType emit on the scope ngInclude was declared in
24388 * Emitted every time the ngInclude content is requested.
24390 * @param {Object} angularEvent Synthetic event object.
24391 * @param {String} src URL of content to load.
24397 * @name ngInclude#$includeContentLoaded
24398 * @eventType emit on the current ngInclude scope
24400 * Emitted every time the ngInclude content is reloaded.
24402 * @param {Object} angularEvent Synthetic event object.
24403 * @param {String} src URL of content to load.
24409 * @name ngInclude#$includeContentError
24410 * @eventType emit on the scope ngInclude was declared in
24412 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24414 * @param {Object} angularEvent Synthetic event object.
24415 * @param {String} src URL of content to load.
24417 var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24418 function($templateRequest, $anchorScroll, $animate) {
24423 transclude: 'element',
24424 controller: angular.noop,
24425 compile: function(element, attr) {
24426 var srcExp = attr.ngInclude || attr.src,
24427 onloadExp = attr.onload || '',
24428 autoScrollExp = attr.autoscroll;
24430 return function(scope, $element, $attr, ctrl, $transclude) {
24431 var changeCounter = 0,
24436 var cleanupLastIncludeContent = function() {
24437 if (previousElement) {
24438 previousElement.remove();
24439 previousElement = null;
24441 if (currentScope) {
24442 currentScope.$destroy();
24443 currentScope = null;
24445 if (currentElement) {
24446 $animate.leave(currentElement).then(function() {
24447 previousElement = null;
24449 previousElement = currentElement;
24450 currentElement = null;
24454 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24455 var afterAnimation = function() {
24456 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24460 var thisChangeId = ++changeCounter;
24463 //set the 2nd param to true to ignore the template request error so that the inner
24464 //contents and scope can be cleaned up.
24465 $templateRequest(src, true).then(function(response) {
24466 if (thisChangeId !== changeCounter) return;
24467 var newScope = scope.$new();
24468 ctrl.template = response;
24470 // Note: This will also link all children of ng-include that were contained in the original
24471 // html. If that content contains controllers, ... they could pollute/change the scope.
24472 // However, using ng-include on an element with additional content does not make sense...
24473 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24474 // function is called before linking the content, which would apply child
24475 // directives to non existing elements.
24476 var clone = $transclude(newScope, function(clone) {
24477 cleanupLastIncludeContent();
24478 $animate.enter(clone, null, $element).then(afterAnimation);
24481 currentScope = newScope;
24482 currentElement = clone;
24484 currentScope.$emit('$includeContentLoaded', src);
24485 scope.$eval(onloadExp);
24487 if (thisChangeId === changeCounter) {
24488 cleanupLastIncludeContent();
24489 scope.$emit('$includeContentError', src);
24492 scope.$emit('$includeContentRequested', src);
24494 cleanupLastIncludeContent();
24495 ctrl.template = null;
24503 // This directive is called during the $transclude call of the first `ngInclude` directive.
24504 // It will replace and compile the content of the element with the loaded template.
24505 // We need this directive so that the element content is already filled when
24506 // the link function of another directive on the same element as ngInclude
24508 var ngIncludeFillContentDirective = ['$compile',
24509 function($compile) {
24513 require: 'ngInclude',
24514 link: function(scope, $element, $attr, ctrl) {
24515 if (/SVG/.test($element[0].toString())) {
24516 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24517 // support innerHTML, so detect this here and try to generate the contents
24520 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24521 function namespaceAdaptedClone(clone) {
24522 $element.append(clone);
24523 }, {futureParentElement: $element});
24527 $element.html(ctrl.template);
24528 $compile($element.contents())(scope);
24539 * The `ngInit` directive allows you to evaluate an expression in the
24542 * <div class="alert alert-danger">
24543 * This directive can be abused to add unnecessary amounts of logic into your templates.
24544 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24545 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24546 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24547 * rather than `ngInit` to initialize values on a scope.
24550 * <div class="alert alert-warning">
24551 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24552 * sure you have parentheses to ensure correct operator precedence:
24553 * <pre class="prettyprint">
24554 * `<div ng-init="test1 = ($index | toString)"></div>`
24561 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24564 <example module="initExample">
24565 <file name="index.html">
24567 angular.module('initExample', [])
24568 .controller('ExampleController', ['$scope', function($scope) {
24569 $scope.list = [['a', 'b'], ['c', 'd']];
24572 <div ng-controller="ExampleController">
24573 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24574 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24575 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24580 <file name="protractor.js" type="protractor">
24581 it('should alias index positions', function() {
24582 var elements = element.all(by.css('.example-init'));
24583 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24584 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24585 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24586 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24591 var ngInitDirective = ngDirective({
24593 compile: function() {
24595 pre: function(scope, element, attrs) {
24596 scope.$eval(attrs.ngInit);
24607 * Text input that converts between a delimited string and an array of strings. The default
24608 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24609 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24611 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24612 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24613 * list item is respected. This implies that the user of the directive is responsible for
24614 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24615 * tab or newline character.
24616 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24617 * when joining the list items back together) and whitespace around each list item is stripped
24618 * before it is added to the model.
24620 * ### Example with Validation
24622 * <example name="ngList-directive" module="listExample">
24623 * <file name="app.js">
24624 * angular.module('listExample', [])
24625 * .controller('ExampleController', ['$scope', function($scope) {
24626 * $scope.names = ['morpheus', 'neo', 'trinity'];
24629 * <file name="index.html">
24630 * <form name="myForm" ng-controller="ExampleController">
24631 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24632 * <span role="alert">
24633 * <span class="error" ng-show="myForm.namesInput.$error.required">
24637 * <tt>names = {{names}}</tt><br/>
24638 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24639 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24640 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24641 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24644 * <file name="protractor.js" type="protractor">
24645 * var listInput = element(by.model('names'));
24646 * var names = element(by.exactBinding('names'));
24647 * var valid = element(by.binding('myForm.namesInput.$valid'));
24648 * var error = element(by.css('span.error'));
24650 * it('should initialize to model', function() {
24651 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24652 * expect(valid.getText()).toContain('true');
24653 * expect(error.getCssValue('display')).toBe('none');
24656 * it('should be invalid if empty', function() {
24657 * listInput.clear();
24658 * listInput.sendKeys('');
24660 * expect(names.getText()).toContain('');
24661 * expect(valid.getText()).toContain('false');
24662 * expect(error.getCssValue('display')).not.toBe('none');
24667 * ### Example - splitting on newline
24668 * <example name="ngList-directive-newlines">
24669 * <file name="index.html">
24670 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
24671 * <pre>{{ list | json }}</pre>
24673 * <file name="protractor.js" type="protractor">
24674 * it("should split the text by newlines", function() {
24675 * var listInput = element(by.model('list'));
24676 * var output = element(by.binding('list | json'));
24677 * listInput.sendKeys('abc\ndef\nghi');
24678 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24684 * @param {string=} ngList optional delimiter that should be used to split the value.
24686 var ngListDirective = function() {
24690 require: 'ngModel',
24691 link: function(scope, element, attr, ctrl) {
24692 // We want to control whitespace trimming so we use this convoluted approach
24693 // to access the ngList attribute, which doesn't pre-trim the attribute
24694 var ngList = element.attr(attr.$attr.ngList) || ', ';
24695 var trimValues = attr.ngTrim !== 'false';
24696 var separator = trimValues ? trim(ngList) : ngList;
24698 var parse = function(viewValue) {
24699 // If the viewValue is invalid (say required but empty) it will be `undefined`
24700 if (isUndefined(viewValue)) return;
24705 forEach(viewValue.split(separator), function(value) {
24706 if (value) list.push(trimValues ? trim(value) : value);
24713 ctrl.$parsers.push(parse);
24714 ctrl.$formatters.push(function(value) {
24715 if (isArray(value)) {
24716 return value.join(ngList);
24722 // Override the standard $isEmpty because an empty array means the input is empty.
24723 ctrl.$isEmpty = function(value) {
24724 return !value || !value.length;
24730 /* global VALID_CLASS: true,
24731 INVALID_CLASS: true,
24732 PRISTINE_CLASS: true,
24734 UNTOUCHED_CLASS: true,
24735 TOUCHED_CLASS: true,
24738 var VALID_CLASS = 'ng-valid',
24739 INVALID_CLASS = 'ng-invalid',
24740 PRISTINE_CLASS = 'ng-pristine',
24741 DIRTY_CLASS = 'ng-dirty',
24742 UNTOUCHED_CLASS = 'ng-untouched',
24743 TOUCHED_CLASS = 'ng-touched',
24744 PENDING_CLASS = 'ng-pending';
24746 var ngModelMinErr = minErr('ngModel');
24750 * @name ngModel.NgModelController
24752 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24753 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24755 * @property {*} $modelValue The value in the model that the control is bound to.
24756 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24757 the control reads value from the DOM. The functions are called in array order, each passing
24758 its return value through to the next. The last return value is forwarded to the
24759 {@link ngModel.NgModelController#$validators `$validators`} collection.
24761 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24764 Returning `undefined` from a parser means a parse error occurred. In that case,
24765 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24766 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24767 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24770 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24771 the model value changes. The functions are called in reverse array order, each passing the value through to the
24772 next. The last return value is used as the actual DOM value.
24773 Used to format / convert values for display in the control.
24775 * function formatter(value) {
24777 * return value.toUpperCase();
24780 * ngModel.$formatters.push(formatter);
24783 * @property {Object.<string, function>} $validators A collection of validators that are applied
24784 * whenever the model value changes. The key value within the object refers to the name of the
24785 * validator while the function refers to the validation operation. The validation operation is
24786 * provided with the model value as an argument and must return a true or false value depending
24787 * on the response of that validation.
24790 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24791 * var value = modelValue || viewValue;
24792 * return /[0-9]+/.test(value) &&
24793 * /[a-z]+/.test(value) &&
24794 * /[A-Z]+/.test(value) &&
24795 * /\W+/.test(value);
24799 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24800 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24801 * is expected to return a promise when it is run during the model validation process. Once the promise
24802 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24803 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24804 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24805 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24806 * will only run once all synchronous validators have passed.
24808 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24809 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24812 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24813 * var value = modelValue || viewValue;
24815 * // Lookup user by username
24816 * return $http.get('/api/users/' + value).
24817 * then(function resolved() {
24818 * //username exists, this means validation fails
24819 * return $q.reject('exists');
24820 * }, function rejected() {
24821 * //username does not exist, therefore this validation passes
24827 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24828 * view value has changed. It is called with no arguments, and its return value is ignored.
24829 * This can be used in place of additional $watches against the model value.
24831 * @property {Object} $error An object hash with all failing validator ids as keys.
24832 * @property {Object} $pending An object hash with all pending validator ids as keys.
24834 * @property {boolean} $untouched True if control has not lost focus yet.
24835 * @property {boolean} $touched True if control has lost focus.
24836 * @property {boolean} $pristine True if user has not interacted with the control yet.
24837 * @property {boolean} $dirty True if user has already interacted with the control.
24838 * @property {boolean} $valid True if there is no error.
24839 * @property {boolean} $invalid True if at least one error on the control.
24840 * @property {string} $name The name attribute of the control.
24844 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24845 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24846 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24847 * listening to DOM events.
24848 * Such DOM related logic should be provided by other directives which make use of
24849 * `NgModelController` for data-binding to control elements.
24850 * Angular provides this DOM logic for most {@link input `input`} elements.
24851 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24852 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24855 * ### Custom Control Example
24856 * This example shows how to use `NgModelController` with a custom control to achieve
24857 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24858 * collaborate together to achieve the desired result.
24860 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24861 * contents be edited in place by the user.
24863 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24864 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24865 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24866 * that content using the `$sce` service.
24868 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24869 <file name="style.css">
24870 [contenteditable] {
24871 border: 1px solid black;
24872 background-color: white;
24877 border: 1px solid red;
24881 <file name="script.js">
24882 angular.module('customControl', ['ngSanitize']).
24883 directive('contenteditable', ['$sce', function($sce) {
24885 restrict: 'A', // only activate on element attribute
24886 require: '?ngModel', // get a hold of NgModelController
24887 link: function(scope, element, attrs, ngModel) {
24888 if (!ngModel) return; // do nothing if no ng-model
24890 // Specify how UI should be updated
24891 ngModel.$render = function() {
24892 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24895 // Listen for change events to enable binding
24896 element.on('blur keyup change', function() {
24897 scope.$evalAsync(read);
24899 read(); // initialize
24901 // Write data to the model
24903 var html = element.html();
24904 // When we clear the content editable the browser leaves a <br> behind
24905 // If strip-br attribute is provided then we strip this out
24906 if ( attrs.stripBr && html == '<br>' ) {
24909 ngModel.$setViewValue(html);
24915 <file name="index.html">
24916 <form name="myForm">
24917 <div contenteditable
24918 name="myWidget" ng-model="userContent"
24920 required>Change me!</div>
24921 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24923 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24926 <file name="protractor.js" type="protractor">
24927 it('should data-bind and become invalid', function() {
24928 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24929 // SafariDriver can't handle contenteditable
24930 // and Firefox driver can't clear contenteditables very well
24933 var contentEditable = element(by.css('[contenteditable]'));
24934 var content = 'Change me!';
24936 expect(contentEditable.getText()).toEqual(content);
24938 contentEditable.clear();
24939 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24940 expect(contentEditable.getText()).toEqual('');
24941 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24948 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24949 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24950 this.$viewValue = Number.NaN;
24951 this.$modelValue = Number.NaN;
24952 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24953 this.$validators = {};
24954 this.$asyncValidators = {};
24955 this.$parsers = [];
24956 this.$formatters = [];
24957 this.$viewChangeListeners = [];
24958 this.$untouched = true;
24959 this.$touched = false;
24960 this.$pristine = true;
24961 this.$dirty = false;
24962 this.$valid = true;
24963 this.$invalid = false;
24964 this.$error = {}; // keep invalid keys here
24965 this.$$success = {}; // keep valid keys here
24966 this.$pending = undefined; // keep pending keys here
24967 this.$name = $interpolate($attr.name || '', false)($scope);
24968 this.$$parentForm = nullFormCtrl;
24970 var parsedNgModel = $parse($attr.ngModel),
24971 parsedNgModelAssign = parsedNgModel.assign,
24972 ngModelGet = parsedNgModel,
24973 ngModelSet = parsedNgModelAssign,
24974 pendingDebounce = null,
24978 this.$$setOptions = function(options) {
24979 ctrl.$options = options;
24980 if (options && options.getterSetter) {
24981 var invokeModelGetter = $parse($attr.ngModel + '()'),
24982 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24984 ngModelGet = function($scope) {
24985 var modelValue = parsedNgModel($scope);
24986 if (isFunction(modelValue)) {
24987 modelValue = invokeModelGetter($scope);
24991 ngModelSet = function($scope, newValue) {
24992 if (isFunction(parsedNgModel($scope))) {
24993 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
24995 parsedNgModelAssign($scope, ctrl.$modelValue);
24998 } else if (!parsedNgModel.assign) {
24999 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
25000 $attr.ngModel, startingTag($element));
25006 * @name ngModel.NgModelController#$render
25009 * Called when the view needs to be updated. It is expected that the user of the ng-model
25010 * directive will implement this method.
25012 * The `$render()` method is invoked in the following situations:
25014 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
25015 * committed value then `$render()` is called to update the input control.
25016 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
25017 * the `$viewValue` are different from last time.
25019 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
25020 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
25021 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
25022 * invoked if you only change a property on the objects.
25024 this.$render = noop;
25028 * @name ngModel.NgModelController#$isEmpty
25031 * This is called when we need to determine if the value of an input is empty.
25033 * For instance, the required directive does this to work out if the input has data or not.
25035 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
25037 * You can override this for input directives whose concept of being empty is different from the
25038 * default. The `checkboxInputType` directive does this because in its case a value of `false`
25041 * @param {*} value The value of the input to check for emptiness.
25042 * @returns {boolean} True if `value` is "empty".
25044 this.$isEmpty = function(value) {
25045 return isUndefined(value) || value === '' || value === null || value !== value;
25048 var currentValidationRunId = 0;
25052 * @name ngModel.NgModelController#$setValidity
25055 * Change the validity state, and notify the form.
25057 * This method can be called within $parsers/$formatters or a custom validation implementation.
25058 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
25059 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
25061 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
25062 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
25063 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25064 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
25065 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
25066 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
25067 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
25068 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
25069 * Skipped is used by Angular when validators do not run because of parse errors and
25070 * when `$asyncValidators` do not run because any of the `$validators` failed.
25072 addSetValidityMethod({
25074 $element: $element,
25075 set: function(object, property) {
25076 object[property] = true;
25078 unset: function(object, property) {
25079 delete object[property];
25086 * @name ngModel.NgModelController#$setPristine
25089 * Sets the control to its pristine state.
25091 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25092 * state (`ng-pristine` class). A model is considered to be pristine when the control
25093 * has not been changed from when first compiled.
25095 this.$setPristine = function() {
25096 ctrl.$dirty = false;
25097 ctrl.$pristine = true;
25098 $animate.removeClass($element, DIRTY_CLASS);
25099 $animate.addClass($element, PRISTINE_CLASS);
25104 * @name ngModel.NgModelController#$setDirty
25107 * Sets the control to its dirty state.
25109 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25110 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25111 * from when first compiled.
25113 this.$setDirty = function() {
25114 ctrl.$dirty = true;
25115 ctrl.$pristine = false;
25116 $animate.removeClass($element, PRISTINE_CLASS);
25117 $animate.addClass($element, DIRTY_CLASS);
25118 ctrl.$$parentForm.$setDirty();
25123 * @name ngModel.NgModelController#$setUntouched
25126 * Sets the control to its untouched state.
25128 * This method can be called to remove the `ng-touched` class and set the control to its
25129 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25130 * by default, however this function can be used to restore that state if the model has
25131 * already been touched by the user.
25133 this.$setUntouched = function() {
25134 ctrl.$touched = false;
25135 ctrl.$untouched = true;
25136 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25141 * @name ngModel.NgModelController#$setTouched
25144 * Sets the control to its touched state.
25146 * This method can be called to remove the `ng-untouched` class and set the control to its
25147 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25148 * first focused the control element and then shifted focus away from the control (blur event).
25150 this.$setTouched = function() {
25151 ctrl.$touched = true;
25152 ctrl.$untouched = false;
25153 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25158 * @name ngModel.NgModelController#$rollbackViewValue
25161 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25162 * which may be caused by a pending debounced event or because the input is waiting for a some
25165 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25166 * as blur you can have a situation where there is a period when the `$viewValue`
25167 * is out of synch with the ngModel's `$modelValue`.
25169 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25170 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25171 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25173 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25174 * input which may have such events pending. This is important in order to make sure that the
25175 * input field will be updated with the new model value and any pending operations are cancelled.
25177 * <example name="ng-model-cancel-update" module="cancel-update-example">
25178 * <file name="app.js">
25179 * angular.module('cancel-update-example', [])
25181 * .controller('CancelUpdateController', ['$scope', function($scope) {
25182 * $scope.resetWithCancel = function(e) {
25183 * if (e.keyCode == 27) {
25184 * $scope.myForm.myInput1.$rollbackViewValue();
25185 * $scope.myValue = '';
25188 * $scope.resetWithoutCancel = function(e) {
25189 * if (e.keyCode == 27) {
25190 * $scope.myValue = '';
25195 * <file name="index.html">
25196 * <div ng-controller="CancelUpdateController">
25197 * <p>Try typing something in each input. See that the model only updates when you
25198 * blur off the input.
25200 * <p>Now see what happens if you start typing then press the Escape key</p>
25202 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25203 * <p id="inputDescription1">With $rollbackViewValue()</p>
25204 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25205 * ng-keydown="resetWithCancel($event)"><br/>
25206 * myValue: "{{ myValue }}"
25208 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25209 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25210 * ng-keydown="resetWithoutCancel($event)"><br/>
25211 * myValue: "{{ myValue }}"
25217 this.$rollbackViewValue = function() {
25218 $timeout.cancel(pendingDebounce);
25219 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25225 * @name ngModel.NgModelController#$validate
25228 * Runs each of the registered validators (first synchronous validators and then
25229 * asynchronous validators).
25230 * If the validity changes to invalid, the model will be set to `undefined`,
25231 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25232 * If the validity changes to valid, it will set the model to the last available valid
25233 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25235 this.$validate = function() {
25236 // ignore $validate before model is initialized
25237 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25241 var viewValue = ctrl.$$lastCommittedViewValue;
25242 // Note: we use the $$rawModelValue as $modelValue might have been
25243 // set to undefined during a view -> model update that found validation
25244 // errors. We can't parse the view here, since that could change
25245 // the model although neither viewValue nor the model on the scope changed
25246 var modelValue = ctrl.$$rawModelValue;
25248 var prevValid = ctrl.$valid;
25249 var prevModelValue = ctrl.$modelValue;
25251 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25253 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25254 // If there was no change in validity, don't update the model
25255 // This prevents changing an invalid modelValue to undefined
25256 if (!allowInvalid && prevValid !== allValid) {
25257 // Note: Don't check ctrl.$valid here, as we could have
25258 // external validators (e.g. calculated on the server),
25259 // that just call $setValidity and need the model value
25260 // to calculate their validity.
25261 ctrl.$modelValue = allValid ? modelValue : undefined;
25263 if (ctrl.$modelValue !== prevModelValue) {
25264 ctrl.$$writeModelToScope();
25271 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25272 currentValidationRunId++;
25273 var localValidationRunId = currentValidationRunId;
25275 // check parser error
25276 if (!processParseErrors()) {
25277 validationDone(false);
25280 if (!processSyncValidators()) {
25281 validationDone(false);
25284 processAsyncValidators();
25286 function processParseErrors() {
25287 var errorKey = ctrl.$$parserName || 'parse';
25288 if (isUndefined(parserValid)) {
25289 setValidity(errorKey, null);
25291 if (!parserValid) {
25292 forEach(ctrl.$validators, function(v, name) {
25293 setValidity(name, null);
25295 forEach(ctrl.$asyncValidators, function(v, name) {
25296 setValidity(name, null);
25299 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25300 setValidity(errorKey, parserValid);
25301 return parserValid;
25306 function processSyncValidators() {
25307 var syncValidatorsValid = true;
25308 forEach(ctrl.$validators, function(validator, name) {
25309 var result = validator(modelValue, viewValue);
25310 syncValidatorsValid = syncValidatorsValid && result;
25311 setValidity(name, result);
25313 if (!syncValidatorsValid) {
25314 forEach(ctrl.$asyncValidators, function(v, name) {
25315 setValidity(name, null);
25322 function processAsyncValidators() {
25323 var validatorPromises = [];
25324 var allValid = true;
25325 forEach(ctrl.$asyncValidators, function(validator, name) {
25326 var promise = validator(modelValue, viewValue);
25327 if (!isPromiseLike(promise)) {
25328 throw ngModelMinErr("$asyncValidators",
25329 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25331 setValidity(name, undefined);
25332 validatorPromises.push(promise.then(function() {
25333 setValidity(name, true);
25334 }, function(error) {
25336 setValidity(name, false);
25339 if (!validatorPromises.length) {
25340 validationDone(true);
25342 $q.all(validatorPromises).then(function() {
25343 validationDone(allValid);
25348 function setValidity(name, isValid) {
25349 if (localValidationRunId === currentValidationRunId) {
25350 ctrl.$setValidity(name, isValid);
25354 function validationDone(allValid) {
25355 if (localValidationRunId === currentValidationRunId) {
25357 doneCallback(allValid);
25364 * @name ngModel.NgModelController#$commitViewValue
25367 * Commit a pending update to the `$modelValue`.
25369 * Updates may be pending by a debounced event or because the input is waiting for a some future
25370 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25371 * usually handles calling this in response to input events.
25373 this.$commitViewValue = function() {
25374 var viewValue = ctrl.$viewValue;
25376 $timeout.cancel(pendingDebounce);
25378 // If the view value has not changed then we should just exit, except in the case where there is
25379 // a native validator on the element. In this case the validation state may have changed even though
25380 // the viewValue has stayed empty.
25381 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25384 ctrl.$$lastCommittedViewValue = viewValue;
25387 if (ctrl.$pristine) {
25390 this.$$parseAndValidate();
25393 this.$$parseAndValidate = function() {
25394 var viewValue = ctrl.$$lastCommittedViewValue;
25395 var modelValue = viewValue;
25396 parserValid = isUndefined(modelValue) ? undefined : true;
25399 for (var i = 0; i < ctrl.$parsers.length; i++) {
25400 modelValue = ctrl.$parsers[i](modelValue);
25401 if (isUndefined(modelValue)) {
25402 parserValid = false;
25407 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25408 // ctrl.$modelValue has not been touched yet...
25409 ctrl.$modelValue = ngModelGet($scope);
25411 var prevModelValue = ctrl.$modelValue;
25412 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25413 ctrl.$$rawModelValue = modelValue;
25415 if (allowInvalid) {
25416 ctrl.$modelValue = modelValue;
25417 writeToModelIfNeeded();
25420 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25421 // This can happen if e.g. $setViewValue is called from inside a parser
25422 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25423 if (!allowInvalid) {
25424 // Note: Don't check ctrl.$valid here, as we could have
25425 // external validators (e.g. calculated on the server),
25426 // that just call $setValidity and need the model value
25427 // to calculate their validity.
25428 ctrl.$modelValue = allValid ? modelValue : undefined;
25429 writeToModelIfNeeded();
25433 function writeToModelIfNeeded() {
25434 if (ctrl.$modelValue !== prevModelValue) {
25435 ctrl.$$writeModelToScope();
25440 this.$$writeModelToScope = function() {
25441 ngModelSet($scope, ctrl.$modelValue);
25442 forEach(ctrl.$viewChangeListeners, function(listener) {
25446 $exceptionHandler(e);
25453 * @name ngModel.NgModelController#$setViewValue
25456 * Update the view value.
25458 * This method should be called when a control wants to change the view value; typically,
25459 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25460 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25461 * calls it when an option is selected.
25463 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25464 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25465 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25466 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25467 * in the `$viewChangeListeners` list, are called.
25469 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25470 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25471 * `updateOn` events is triggered on the DOM element.
25472 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25473 * directive is used with a custom debounce for this particular event.
25474 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25475 * is specified, once the timer runs out.
25477 * When used with standard inputs, the view value will always be a string (which is in some cases
25478 * parsed into another type, such as a `Date` object for `input[date]`.)
25479 * However, custom controls might also pass objects to this method. In this case, we should make
25480 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25481 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25482 * the property of the object then ngModel will not realise that the object has changed and
25483 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25484 * not change properties of the copy once it has been passed to `$setViewValue`.
25485 * Otherwise you may cause the model value on the scope to change incorrectly.
25487 * <div class="alert alert-info">
25488 * In any case, the value passed to the method should always reflect the current value
25489 * of the control. For example, if you are calling `$setViewValue` for an input element,
25490 * you should pass the input DOM value. Otherwise, the control and the scope model become
25491 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25492 * the control's DOM value in any way. If we want to change the control's DOM value
25493 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25494 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25495 * to update the DOM, and finally call `$validate` on it.
25498 * @param {*} value value from the view.
25499 * @param {string} trigger Event that triggered the update.
25501 this.$setViewValue = function(value, trigger) {
25502 ctrl.$viewValue = value;
25503 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25504 ctrl.$$debounceViewValueCommit(trigger);
25508 this.$$debounceViewValueCommit = function(trigger) {
25509 var debounceDelay = 0,
25510 options = ctrl.$options,
25513 if (options && isDefined(options.debounce)) {
25514 debounce = options.debounce;
25515 if (isNumber(debounce)) {
25516 debounceDelay = debounce;
25517 } else if (isNumber(debounce[trigger])) {
25518 debounceDelay = debounce[trigger];
25519 } else if (isNumber(debounce['default'])) {
25520 debounceDelay = debounce['default'];
25524 $timeout.cancel(pendingDebounce);
25525 if (debounceDelay) {
25526 pendingDebounce = $timeout(function() {
25527 ctrl.$commitViewValue();
25529 } else if ($rootScope.$$phase) {
25530 ctrl.$commitViewValue();
25532 $scope.$apply(function() {
25533 ctrl.$commitViewValue();
25539 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25540 // 1. scope value is 'a'
25541 // 2. user enters 'b'
25542 // 3. ng-change kicks in and reverts scope value to 'a'
25543 // -> scope value did not change since the last digest as
25544 // ng-change executes in apply phase
25545 // 4. view should be changed back to 'a'
25546 $scope.$watch(function ngModelWatch() {
25547 var modelValue = ngModelGet($scope);
25549 // if scope model value and ngModel value are out of sync
25550 // TODO(perf): why not move this to the action fn?
25551 if (modelValue !== ctrl.$modelValue &&
25552 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25553 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25555 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25556 parserValid = undefined;
25558 var formatters = ctrl.$formatters,
25559 idx = formatters.length;
25561 var viewValue = modelValue;
25563 viewValue = formatters[idx](viewValue);
25565 if (ctrl.$viewValue !== viewValue) {
25566 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25569 ctrl.$$runValidators(modelValue, viewValue, noop);
25586 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25587 * property on the scope using {@link ngModel.NgModelController NgModelController},
25588 * which is created and exposed by this directive.
25590 * `ngModel` is responsible for:
25592 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25594 * - Providing validation behavior (i.e. required, number, email, url).
25595 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25596 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25597 * - Registering the control with its parent {@link ng.directive:form form}.
25599 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25600 * current scope. If the property doesn't already exist on this scope, it will be created
25601 * implicitly and added to the scope.
25603 * For best practices on using `ngModel`, see:
25605 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25607 * For basic examples, how to use `ngModel`, see:
25609 * - {@link ng.directive:input input}
25610 * - {@link input[text] text}
25611 * - {@link input[checkbox] checkbox}
25612 * - {@link input[radio] radio}
25613 * - {@link input[number] number}
25614 * - {@link input[email] email}
25615 * - {@link input[url] url}
25616 * - {@link input[date] date}
25617 * - {@link input[datetime-local] datetime-local}
25618 * - {@link input[time] time}
25619 * - {@link input[month] month}
25620 * - {@link input[week] week}
25621 * - {@link ng.directive:select select}
25622 * - {@link ng.directive:textarea textarea}
25625 * The following CSS classes are added and removed on the associated input/select/textarea element
25626 * depending on the validity of the model.
25628 * - `ng-valid`: the model is valid
25629 * - `ng-invalid`: the model is invalid
25630 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25631 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25632 * - `ng-pristine`: the control hasn't been interacted with yet
25633 * - `ng-dirty`: the control has been interacted with
25634 * - `ng-touched`: the control has been blurred
25635 * - `ng-untouched`: the control hasn't been blurred
25636 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25638 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25640 * ## Animation Hooks
25642 * Animations within models are triggered when any of the associated CSS classes are added and removed
25643 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25644 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25645 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25646 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25648 * The following example shows a simple way to utilize CSS transitions to style an input element
25649 * that has been rendered as invalid after it has been validated:
25652 * //be sure to include ngAnimate as a module to hook into more
25653 * //advanced animations
25655 * transition:0.5s linear all;
25656 * background: white;
25658 * .my-input.ng-invalid {
25665 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25666 <file name="index.html">
25668 angular.module('inputExample', [])
25669 .controller('ExampleController', ['$scope', function($scope) {
25675 transition:all linear 0.5s;
25676 background: transparent;
25678 .my-input.ng-invalid {
25683 <p id="inputDescription">
25684 Update input to see transitions when valid/invalid.
25685 Integer is a valid value.
25687 <form name="testForm" ng-controller="ExampleController">
25688 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25689 aria-describedby="inputDescription" />
25694 * ## Binding to a getter/setter
25696 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25697 * function that returns a representation of the model when called with zero arguments, and sets
25698 * the internal state of a model when called with an argument. It's sometimes useful to use this
25699 * for models that have an internal representation that's different from what the model exposes
25702 * <div class="alert alert-success">
25703 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25704 * frequently than other parts of your code.
25707 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25708 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25709 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25710 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25712 * The following example shows how to use `ngModel` with a getter/setter:
25715 * <example name="ngModel-getter-setter" module="getterSetterExample">
25716 <file name="index.html">
25717 <div ng-controller="ExampleController">
25718 <form name="userForm">
25720 <input type="text" name="userName"
25721 ng-model="user.name"
25722 ng-model-options="{ getterSetter: true }" />
25725 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25728 <file name="app.js">
25729 angular.module('getterSetterExample', [])
25730 .controller('ExampleController', ['$scope', function($scope) {
25731 var _name = 'Brian';
25733 name: function(newName) {
25734 // Note that newName can be undefined for two reasons:
25735 // 1. Because it is called as a getter and thus called with no arguments
25736 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25737 // input is invalid
25738 return arguments.length ? (_name = newName) : _name;
25745 var ngModelDirective = ['$rootScope', function($rootScope) {
25748 require: ['ngModel', '^?form', '^?ngModelOptions'],
25749 controller: NgModelController,
25750 // Prelink needs to run before any input directive
25751 // so that we can set the NgModelOptions in NgModelController
25752 // before anyone else uses it.
25754 compile: function ngModelCompile(element) {
25755 // Setup initial state of the control
25756 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25759 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25760 var modelCtrl = ctrls[0],
25761 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25763 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25765 // notify others, especially parent forms
25766 formCtrl.$addControl(modelCtrl);
25768 attr.$observe('name', function(newValue) {
25769 if (modelCtrl.$name !== newValue) {
25770 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25774 scope.$on('$destroy', function() {
25775 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25778 post: function ngModelPostLink(scope, element, attr, ctrls) {
25779 var modelCtrl = ctrls[0];
25780 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25781 element.on(modelCtrl.$options.updateOn, function(ev) {
25782 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25786 element.on('blur', function(ev) {
25787 if (modelCtrl.$touched) return;
25789 if ($rootScope.$$phase) {
25790 scope.$evalAsync(modelCtrl.$setTouched);
25792 scope.$apply(modelCtrl.$setTouched);
25801 var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25805 * @name ngModelOptions
25808 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25809 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25810 * takes place when a timer expires; this timer will be reset after another change takes place.
25812 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25813 * be different from the value in the actual model. This means that if you update the model you
25814 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25815 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25817 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25818 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25819 * important because `form` controllers are published to the related scope under the name in their
25820 * `name` attribute.
25822 * Any pending changes will take place immediately when an enclosing form is submitted via the
25823 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25824 * to have access to the updated model.
25826 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25828 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25829 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25830 * events using an space delimited list. There is a special event called `default` that
25831 * matches the default events belonging of the control.
25832 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25833 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25834 * custom value for each event. For example:
25835 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25836 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25837 * not validate correctly instead of the default behavior of setting the model to undefined.
25838 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25839 `ngModel` as getters/setters.
25840 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25841 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25842 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25843 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25844 * If not specified, the timezone of the browser will be used.
25848 The following example shows how to override immediate updates. Changes on the inputs within the
25849 form will update the model only when the control loses focus (blur event). If `escape` key is
25850 pressed while the input field is focused, the value is reset to the value in the current model.
25852 <example name="ngModelOptions-directive-blur" module="optionsExample">
25853 <file name="index.html">
25854 <div ng-controller="ExampleController">
25855 <form name="userForm">
25857 <input type="text" name="userName"
25858 ng-model="user.name"
25859 ng-model-options="{ updateOn: 'blur' }"
25860 ng-keyup="cancel($event)" />
25863 <input type="text" ng-model="user.data" />
25866 <pre>user.name = <span ng-bind="user.name"></span></pre>
25867 <pre>user.data = <span ng-bind="user.data"></span></pre>
25870 <file name="app.js">
25871 angular.module('optionsExample', [])
25872 .controller('ExampleController', ['$scope', function($scope) {
25873 $scope.user = { name: 'John', data: '' };
25875 $scope.cancel = function(e) {
25876 if (e.keyCode == 27) {
25877 $scope.userForm.userName.$rollbackViewValue();
25882 <file name="protractor.js" type="protractor">
25883 var model = element(by.binding('user.name'));
25884 var input = element(by.model('user.name'));
25885 var other = element(by.model('user.data'));
25887 it('should allow custom events', function() {
25888 input.sendKeys(' Doe');
25890 expect(model.getText()).toEqual('John');
25892 expect(model.getText()).toEqual('John Doe');
25895 it('should $rollbackViewValue when model changes', function() {
25896 input.sendKeys(' Doe');
25897 expect(input.getAttribute('value')).toEqual('John Doe');
25898 input.sendKeys(protractor.Key.ESCAPE);
25899 expect(input.getAttribute('value')).toEqual('John');
25901 expect(model.getText()).toEqual('John');
25906 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25907 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25909 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25910 <file name="index.html">
25911 <div ng-controller="ExampleController">
25912 <form name="userForm">
25914 <input type="text" name="userName"
25915 ng-model="user.name"
25916 ng-model-options="{ debounce: 1000 }" />
25918 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25921 <pre>user.name = <span ng-bind="user.name"></span></pre>
25924 <file name="app.js">
25925 angular.module('optionsExample', [])
25926 .controller('ExampleController', ['$scope', function($scope) {
25927 $scope.user = { name: 'Igor' };
25932 This one shows how to bind to getter/setters:
25934 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25935 <file name="index.html">
25936 <div ng-controller="ExampleController">
25937 <form name="userForm">
25939 <input type="text" name="userName"
25940 ng-model="user.name"
25941 ng-model-options="{ getterSetter: true }" />
25944 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25947 <file name="app.js">
25948 angular.module('getterSetterExample', [])
25949 .controller('ExampleController', ['$scope', function($scope) {
25950 var _name = 'Brian';
25952 name: function(newName) {
25953 // Note that newName can be undefined for two reasons:
25954 // 1. Because it is called as a getter and thus called with no arguments
25955 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25956 // input is invalid
25957 return arguments.length ? (_name = newName) : _name;
25964 var ngModelOptionsDirective = function() {
25967 controller: ['$scope', '$attrs', function($scope, $attrs) {
25969 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25970 // Allow adding/overriding bound events
25971 if (isDefined(this.$options.updateOn)) {
25972 this.$options.updateOnDefault = false;
25973 // extract "default" pseudo-event from list of events that can trigger a model update
25974 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25975 that.$options.updateOnDefault = true;
25979 this.$options.updateOnDefault = true;
25988 function addSetValidityMethod(context) {
25989 var ctrl = context.ctrl,
25990 $element = context.$element,
25993 unset = context.unset,
25994 $animate = context.$animate;
25996 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
25998 ctrl.$setValidity = setValidity;
26000 function setValidity(validationErrorKey, state, controller) {
26001 if (isUndefined(state)) {
26002 createAndSet('$pending', validationErrorKey, controller);
26004 unsetAndCleanup('$pending', validationErrorKey, controller);
26006 if (!isBoolean(state)) {
26007 unset(ctrl.$error, validationErrorKey, controller);
26008 unset(ctrl.$$success, validationErrorKey, controller);
26011 unset(ctrl.$error, validationErrorKey, controller);
26012 set(ctrl.$$success, validationErrorKey, controller);
26014 set(ctrl.$error, validationErrorKey, controller);
26015 unset(ctrl.$$success, validationErrorKey, controller);
26018 if (ctrl.$pending) {
26019 cachedToggleClass(PENDING_CLASS, true);
26020 ctrl.$valid = ctrl.$invalid = undefined;
26021 toggleValidationCss('', null);
26023 cachedToggleClass(PENDING_CLASS, false);
26024 ctrl.$valid = isObjectEmpty(ctrl.$error);
26025 ctrl.$invalid = !ctrl.$valid;
26026 toggleValidationCss('', ctrl.$valid);
26029 // re-read the state as the set/unset methods could have
26030 // combined state in ctrl.$error[validationError] (used for forms),
26031 // where setting/unsetting only increments/decrements the value,
26032 // and does not replace it.
26034 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
26035 combinedState = undefined;
26036 } else if (ctrl.$error[validationErrorKey]) {
26037 combinedState = false;
26038 } else if (ctrl.$$success[validationErrorKey]) {
26039 combinedState = true;
26041 combinedState = null;
26044 toggleValidationCss(validationErrorKey, combinedState);
26045 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
26048 function createAndSet(name, value, controller) {
26052 set(ctrl[name], value, controller);
26055 function unsetAndCleanup(name, value, controller) {
26057 unset(ctrl[name], value, controller);
26059 if (isObjectEmpty(ctrl[name])) {
26060 ctrl[name] = undefined;
26064 function cachedToggleClass(className, switchValue) {
26065 if (switchValue && !classCache[className]) {
26066 $animate.addClass($element, className);
26067 classCache[className] = true;
26068 } else if (!switchValue && classCache[className]) {
26069 $animate.removeClass($element, className);
26070 classCache[className] = false;
26074 function toggleValidationCss(validationErrorKey, isValid) {
26075 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26077 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26078 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26082 function isObjectEmpty(obj) {
26084 for (var prop in obj) {
26085 if (obj.hasOwnProperty(prop)) {
26095 * @name ngNonBindable
26100 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26101 * DOM element. This is useful if the element contains what appears to be Angular directives and
26102 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26103 * displays snippets of code, for instance.
26108 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26109 * but the one wrapped in `ngNonBindable` is left alone.
26113 <file name="index.html">
26114 <div>Normal: {{1 + 2}}</div>
26115 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26117 <file name="protractor.js" type="protractor">
26118 it('should check ng-non-bindable', function() {
26119 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26120 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26125 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26127 /* global jqLiteRemove */
26129 var ngOptionsMinErr = minErr('ngOptions');
26138 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26139 * elements for the `<select>` element using the array or object obtained by evaluating the
26140 * `ngOptions` comprehension expression.
26142 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26143 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26144 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26145 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26146 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26147 * to a non-string value. This is because an option element can only be bound to string values at
26150 * When an item in the `<select>` menu is selected, the array element or object property
26151 * represented by the selected option will be bound to the model identified by the `ngModel`
26154 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26155 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26156 * option. See example below for demonstration.
26158 * ## Complex Models (objects or collections)
26160 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26161 * binding the select to a model that is an object or a collection.
26163 * One issue occurs if you want to preselect an option. For example, if you set
26164 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26165 * because the objects are not identical. So by default, you should always reference the item in your collection
26166 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26168 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26169 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26170 * collection items have an id property, you would `track by item.id`.
26172 * A different issue with objects or collections is that ngModel won't detect if an object property or
26173 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26174 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26175 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26176 * has not changed identity, but only a property on the object or an item in the collection changes.
26178 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26179 * if the model is an array). This means that changing a property deeper than the first level inside the
26180 * object/collection will not trigger a re-rendering.
26182 * ## `select` **`as`**
26184 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26185 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26186 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26187 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26190 * ### `select` **`as`** and **`track by`**
26192 * <div class="alert alert-warning">
26193 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26196 * Given this array of items on the $scope:
26199 * $scope.items = [{
26202 * subItem: { name: 'aSubItem' }
26206 * subItem: { name: 'bSubItem' }
26213 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26216 * $scope.selected = $scope.items[0];
26219 * but this will not work:
26222 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26225 * $scope.selected = $scope.items[0].subItem;
26228 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26229 * `items` array. Because the selected option has been set programmatically in the controller, the
26230 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26231 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26232 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26233 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26234 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26237 * @param {string} ngModel Assignable angular expression to data-bind to.
26238 * @param {string=} name Property name of the form under which the control is published.
26239 * @param {string=} required The control is considered valid only if value is entered.
26240 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26241 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26242 * `required` when you want to data-bind to the `required` attribute.
26243 * @param {comprehension_expression=} ngOptions in one of the following forms:
26245 * * for array data sources:
26246 * * `label` **`for`** `value` **`in`** `array`
26247 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26248 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26249 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26250 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26251 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26252 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26253 * (for including a filter with `track by`)
26254 * * for object data sources:
26255 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26256 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26257 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26258 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26259 * * `select` **`as`** `label` **`group by`** `group`
26260 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26261 * * `select` **`as`** `label` **`disable when`** `disable`
26262 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26266 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26267 * * `value`: local variable which will refer to each item in the `array` or each property value
26268 * of `object` during iteration.
26269 * * `key`: local variable which will refer to a property name in `object` during iteration.
26270 * * `label`: The result of this expression will be the label for `<option>` element. The
26271 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26272 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26273 * element. If not specified, `select` expression will default to `value`.
26274 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26276 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26277 * element. Return `true` to disable.
26278 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26279 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26280 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26281 * even when the options are recreated (e.g. reloaded from the server).
26284 <example module="selectExample">
26285 <file name="index.html">
26287 angular.module('selectExample', [])
26288 .controller('ExampleController', ['$scope', function($scope) {
26290 {name:'black', shade:'dark'},
26291 {name:'white', shade:'light', notAnOption: true},
26292 {name:'red', shade:'dark'},
26293 {name:'blue', shade:'dark', notAnOption: true},
26294 {name:'yellow', shade:'light', notAnOption: false}
26296 $scope.myColor = $scope.colors[2]; // red
26299 <div ng-controller="ExampleController">
26301 <li ng-repeat="color in colors">
26302 <label>Name: <input ng-model="color.name"></label>
26303 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26304 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26307 <button ng-click="colors.push({})">add</button>
26311 <label>Color (null not allowed):
26312 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26314 <label>Color (null allowed):
26315 <span class="nullable">
26316 <select ng-model="myColor" ng-options="color.name for color in colors">
26317 <option value="">-- choose color --</option>
26319 </span></label><br/>
26321 <label>Color grouped by shade:
26322 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26326 <label>Color grouped by shade, with some disabled:
26327 <select ng-model="myColor"
26328 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26334 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26337 Currently selected: {{ {selected_color:myColor} }}
26338 <div style="border:solid 1px black; height:20px"
26339 ng-style="{'background-color':myColor.name}">
26343 <file name="protractor.js" type="protractor">
26344 it('should check ng-options', function() {
26345 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26346 element.all(by.model('myColor')).first().click();
26347 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26348 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26349 element(by.css('.nullable select[ng-model="myColor"]')).click();
26350 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26351 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26357 // jshint maxlen: false
26358 // //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26359 var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
26360 // 1: value expression (valueFn)
26361 // 2: label expression (displayFn)
26362 // 3: group by expression (groupByFn)
26363 // 4: disable when expression (disableWhenFn)
26364 // 5: array item variable name
26365 // 6: object item key variable name
26366 // 7: object item value variable name
26367 // 8: collection expression
26368 // 9: track by expression
26369 // jshint maxlen: 100
26372 var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26374 function parseOptionsExpression(optionsExp, selectElement, scope) {
26376 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26378 throw ngOptionsMinErr('iexp',
26379 "Expected expression in form of " +
26380 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26381 " but got '{0}'. Element: {1}",
26382 optionsExp, startingTag(selectElement));
26385 // Extract the parts from the ngOptions expression
26387 // The variable name for the value of the item in the collection
26388 var valueName = match[5] || match[7];
26389 // The variable name for the key of the item in the collection
26390 var keyName = match[6];
26392 // An expression that generates the viewValue for an option if there is a label expression
26393 var selectAs = / as /.test(match[0]) && match[1];
26394 // An expression that is used to track the id of each object in the options collection
26395 var trackBy = match[9];
26396 // An expression that generates the viewValue for an option if there is no label expression
26397 var valueFn = $parse(match[2] ? match[1] : valueName);
26398 var selectAsFn = selectAs && $parse(selectAs);
26399 var viewValueFn = selectAsFn || valueFn;
26400 var trackByFn = trackBy && $parse(trackBy);
26402 // Get the value by which we are going to track the option
26403 // if we have a trackFn then use that (passing scope and locals)
26404 // otherwise just hash the given viewValue
26405 var getTrackByValueFn = trackBy ?
26406 function(value, locals) { return trackByFn(scope, locals); } :
26407 function getHashOfValue(value) { return hashKey(value); };
26408 var getTrackByValue = function(value, key) {
26409 return getTrackByValueFn(value, getLocals(value, key));
26412 var displayFn = $parse(match[2] || match[1]);
26413 var groupByFn = $parse(match[3] || '');
26414 var disableWhenFn = $parse(match[4] || '');
26415 var valuesFn = $parse(match[8]);
26418 var getLocals = keyName ? function(value, key) {
26419 locals[keyName] = key;
26420 locals[valueName] = value;
26422 } : function(value) {
26423 locals[valueName] = value;
26428 function Option(selectValue, viewValue, label, group, disabled) {
26429 this.selectValue = selectValue;
26430 this.viewValue = viewValue;
26431 this.label = label;
26432 this.group = group;
26433 this.disabled = disabled;
26436 function getOptionValuesKeys(optionValues) {
26437 var optionValuesKeys;
26439 if (!keyName && isArrayLike(optionValues)) {
26440 optionValuesKeys = optionValues;
26442 // if object, extract keys, in enumeration order, unsorted
26443 optionValuesKeys = [];
26444 for (var itemKey in optionValues) {
26445 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26446 optionValuesKeys.push(itemKey);
26450 return optionValuesKeys;
26455 getTrackByValue: getTrackByValue,
26456 getWatchables: $parse(valuesFn, function(optionValues) {
26457 // Create a collection of things that we would like to watch (watchedArray)
26458 // so that they can all be watched using a single $watchCollection
26459 // that only runs the handler once if anything changes
26460 var watchedArray = [];
26461 optionValues = optionValues || [];
26463 var optionValuesKeys = getOptionValuesKeys(optionValues);
26464 var optionValuesLength = optionValuesKeys.length;
26465 for (var index = 0; index < optionValuesLength; index++) {
26466 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26467 var value = optionValues[key];
26469 var locals = getLocals(optionValues[key], key);
26470 var selectValue = getTrackByValueFn(optionValues[key], locals);
26471 watchedArray.push(selectValue);
26473 // Only need to watch the displayFn if there is a specific label expression
26474 if (match[2] || match[1]) {
26475 var label = displayFn(scope, locals);
26476 watchedArray.push(label);
26479 // Only need to watch the disableWhenFn if there is a specific disable expression
26481 var disableWhen = disableWhenFn(scope, locals);
26482 watchedArray.push(disableWhen);
26485 return watchedArray;
26488 getOptions: function() {
26490 var optionItems = [];
26491 var selectValueMap = {};
26493 // The option values were already computed in the `getWatchables` fn,
26494 // which must have been called to trigger `getOptions`
26495 var optionValues = valuesFn(scope) || [];
26496 var optionValuesKeys = getOptionValuesKeys(optionValues);
26497 var optionValuesLength = optionValuesKeys.length;
26499 for (var index = 0; index < optionValuesLength; index++) {
26500 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26501 var value = optionValues[key];
26502 var locals = getLocals(value, key);
26503 var viewValue = viewValueFn(scope, locals);
26504 var selectValue = getTrackByValueFn(viewValue, locals);
26505 var label = displayFn(scope, locals);
26506 var group = groupByFn(scope, locals);
26507 var disabled = disableWhenFn(scope, locals);
26508 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26510 optionItems.push(optionItem);
26511 selectValueMap[selectValue] = optionItem;
26515 items: optionItems,
26516 selectValueMap: selectValueMap,
26517 getOptionFromViewValue: function(value) {
26518 return selectValueMap[getTrackByValue(value)];
26520 getViewValueFromOption: function(option) {
26521 // If the viewValue could be an object that may be mutated by the application,
26522 // we need to make a copy and not return the reference to the value on the option.
26523 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26531 // we can't just jqLite('<option>') since jqLite is not smart enough
26532 // to create it in <select> and IE barfs otherwise.
26533 var optionTemplate = document.createElement('option'),
26534 optGroupTemplate = document.createElement('optgroup');
26537 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26539 // if ngModel is not defined, we don't need to do anything
26540 var ngModelCtrl = ctrls[1];
26541 if (!ngModelCtrl) return;
26543 var selectCtrl = ctrls[0];
26544 var multiple = attr.multiple;
26546 // The emptyOption allows the application developer to provide their own custom "empty"
26547 // option when the viewValue does not match any of the option values.
26549 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26550 if (children[i].value === '') {
26551 emptyOption = children.eq(i);
26556 var providedEmptyOption = !!emptyOption;
26558 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26559 unknownOption.val('?');
26562 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26565 var renderEmptyOption = function() {
26566 if (!providedEmptyOption) {
26567 selectElement.prepend(emptyOption);
26569 selectElement.val('');
26570 emptyOption.prop('selected', true); // needed for IE
26571 emptyOption.attr('selected', true);
26574 var removeEmptyOption = function() {
26575 if (!providedEmptyOption) {
26576 emptyOption.remove();
26581 var renderUnknownOption = function() {
26582 selectElement.prepend(unknownOption);
26583 selectElement.val('?');
26584 unknownOption.prop('selected', true); // needed for IE
26585 unknownOption.attr('selected', true);
26588 var removeUnknownOption = function() {
26589 unknownOption.remove();
26592 // Update the controller methods for multiple selectable options
26595 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26596 var option = options.getOptionFromViewValue(value);
26598 if (option && !option.disabled) {
26599 if (selectElement[0].value !== option.selectValue) {
26600 removeUnknownOption();
26601 removeEmptyOption();
26603 selectElement[0].value = option.selectValue;
26604 option.element.selected = true;
26605 option.element.setAttribute('selected', 'selected');
26608 if (value === null || providedEmptyOption) {
26609 removeUnknownOption();
26610 renderEmptyOption();
26612 removeEmptyOption();
26613 renderUnknownOption();
26618 selectCtrl.readValue = function readNgOptionsValue() {
26620 var selectedOption = options.selectValueMap[selectElement.val()];
26622 if (selectedOption && !selectedOption.disabled) {
26623 removeEmptyOption();
26624 removeUnknownOption();
26625 return options.getViewValueFromOption(selectedOption);
26630 // If we are using `track by` then we must watch the tracked value on the model
26631 // since ngModel only watches for object identity change
26632 if (ngOptions.trackBy) {
26634 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26635 function() { ngModelCtrl.$render(); }
26641 ngModelCtrl.$isEmpty = function(value) {
26642 return !value || value.length === 0;
26646 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26647 options.items.forEach(function(option) {
26648 option.element.selected = false;
26652 value.forEach(function(item) {
26653 var option = options.getOptionFromViewValue(item);
26654 if (option && !option.disabled) option.element.selected = true;
26660 selectCtrl.readValue = function readNgOptionsMultiple() {
26661 var selectedValues = selectElement.val() || [],
26664 forEach(selectedValues, function(value) {
26665 var option = options.selectValueMap[value];
26666 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26672 // If we are using `track by` then we must watch these tracked values on the model
26673 // since ngModel only watches for object identity change
26674 if (ngOptions.trackBy) {
26676 scope.$watchCollection(function() {
26677 if (isArray(ngModelCtrl.$viewValue)) {
26678 return ngModelCtrl.$viewValue.map(function(value) {
26679 return ngOptions.getTrackByValue(value);
26683 ngModelCtrl.$render();
26690 if (providedEmptyOption) {
26692 // we need to remove it before calling selectElement.empty() because otherwise IE will
26693 // remove the label from the element. wtf?
26694 emptyOption.remove();
26696 // compile the element since there might be bindings in it
26697 $compile(emptyOption)(scope);
26699 // remove the class, which is added automatically because we recompile the element and it
26700 // becomes the compilation root
26701 emptyOption.removeClass('ng-scope');
26703 emptyOption = jqLite(optionTemplate.cloneNode(false));
26706 // We need to do this here to ensure that the options object is defined
26707 // when we first hit it in writeNgOptionsValue
26710 // We will re-render the option elements if the option values or labels change
26711 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26713 // ------------------------------------------------------------------ //
26716 function updateOptionElement(option, element) {
26717 option.element = element;
26718 element.disabled = option.disabled;
26719 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26720 // selects in certain circumstances when multiple selects are next to each other and display
26721 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26722 // See https://github.com/angular/angular.js/issues/11314 for more info.
26723 // This is unfortunately untestable with unit / e2e tests
26724 if (option.label !== element.label) {
26725 element.label = option.label;
26726 element.textContent = option.label;
26728 if (option.value !== element.value) element.value = option.selectValue;
26731 function addOrReuseElement(parent, current, type, templateElement) {
26733 // Check whether we can reuse the next element
26734 if (current && lowercase(current.nodeName) === type) {
26735 // The next element is the right type so reuse it
26738 // The next element is not the right type so create a new one
26739 element = templateElement.cloneNode(false);
26741 // There are no more elements so just append it to the select
26742 parent.appendChild(element);
26744 // The next element is not a group so insert the new one
26745 parent.insertBefore(element, current);
26752 function removeExcessElements(current) {
26755 next = current.nextSibling;
26756 jqLiteRemove(current);
26762 function skipEmptyAndUnknownOptions(current) {
26763 var emptyOption_ = emptyOption && emptyOption[0];
26764 var unknownOption_ = unknownOption && unknownOption[0];
26766 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26767 // because the compiled empty option might have been replaced by a comment because
26768 // it had an "element" transclusion directive on it (such as ngIf)
26769 if (emptyOption_ || unknownOption_) {
26771 (current === emptyOption_ ||
26772 current === unknownOption_ ||
26773 current.nodeType === NODE_TYPE_COMMENT ||
26774 current.value === '')) {
26775 current = current.nextSibling;
26782 function updateOptions() {
26784 var previousValue = options && selectCtrl.readValue();
26786 options = ngOptions.getOptions();
26789 var currentElement = selectElement[0].firstChild;
26791 // Ensure that the empty option is always there if it was explicitly provided
26792 if (providedEmptyOption) {
26793 selectElement.prepend(emptyOption);
26796 currentElement = skipEmptyAndUnknownOptions(currentElement);
26798 options.items.forEach(function updateOption(option) {
26803 if (option.group) {
26805 // This option is to live in a group
26806 // See if we have already created this group
26807 group = groupMap[option.group];
26811 // We have not already created this group
26812 groupElement = addOrReuseElement(selectElement[0],
26816 // Move to the next element
26817 currentElement = groupElement.nextSibling;
26819 // Update the label on the group element
26820 groupElement.label = option.group;
26822 // Store it for use later
26823 group = groupMap[option.group] = {
26824 groupElement: groupElement,
26825 currentOptionElement: groupElement.firstChild
26830 // So now we have a group for this option we add the option to the group
26831 optionElement = addOrReuseElement(group.groupElement,
26832 group.currentOptionElement,
26835 updateOptionElement(option, optionElement);
26836 // Move to the next element
26837 group.currentOptionElement = optionElement.nextSibling;
26841 // This option is not in a group
26842 optionElement = addOrReuseElement(selectElement[0],
26846 updateOptionElement(option, optionElement);
26847 // Move to the next element
26848 currentElement = optionElement.nextSibling;
26853 // Now remove all excess options and group
26854 Object.keys(groupMap).forEach(function(key) {
26855 removeExcessElements(groupMap[key].currentOptionElement);
26857 removeExcessElements(currentElement);
26859 ngModelCtrl.$render();
26861 // Check to see if the value has changed due to the update to the options
26862 if (!ngModelCtrl.$isEmpty(previousValue)) {
26863 var nextValue = selectCtrl.readValue();
26864 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26865 ngModelCtrl.$setViewValue(nextValue);
26866 ngModelCtrl.$render();
26876 require: ['select', '?ngModel'],
26878 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26879 // Deactivate the SelectController.register method to prevent
26880 // option directives from accidentally registering themselves
26881 // (and unwanted $destroy handlers etc.)
26882 ctrls[0].registerOption = noop;
26884 post: ngOptionsPostLink
26891 * @name ngPluralize
26895 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26896 * These rules are bundled with angular.js, but can be overridden
26897 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26898 * by specifying the mappings between
26899 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26900 * and the strings to be displayed.
26902 * # Plural categories and explicit number rules
26904 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26905 * in Angular's default en-US locale: "one" and "other".
26907 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26908 * any number that is not 1), an explicit number rule can only match one number. For example, the
26909 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26910 * and explicit number rules throughout the rest of this documentation.
26912 * # Configuring ngPluralize
26913 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26914 * You can also provide an optional attribute, `offset`.
26916 * The value of the `count` attribute can be either a string or an {@link guide/expression
26917 * Angular expression}; these are evaluated on the current scope for its bound value.
26919 * The `when` attribute specifies the mappings between plural categories and the actual
26920 * string to be displayed. The value of the attribute should be a JSON object.
26922 * The following example shows how to configure ngPluralize:
26925 * <ng-pluralize count="personCount"
26926 when="{'0': 'Nobody is viewing.',
26927 * 'one': '1 person is viewing.',
26928 * 'other': '{} people are viewing.'}">
26932 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26933 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26934 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26935 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26936 * show "a dozen people are viewing".
26938 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26939 * into pluralized strings. In the previous example, Angular will replace `{}` with
26940 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26941 * for <span ng-non-bindable>{{numberExpression}}</span>.
26943 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26944 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26946 * # Configuring ngPluralize with offset
26947 * The `offset` attribute allows further customization of pluralized text, which can result in
26948 * a better user experience. For example, instead of the message "4 people are viewing this document",
26949 * you might display "John, Kate and 2 others are viewing this document".
26950 * The offset attribute allows you to offset a number by any desired value.
26951 * Let's take a look at an example:
26954 * <ng-pluralize count="personCount" offset=2
26955 * when="{'0': 'Nobody is viewing.',
26956 * '1': '{{person1}} is viewing.',
26957 * '2': '{{person1}} and {{person2}} are viewing.',
26958 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26959 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26963 * Notice that we are still using two plural categories(one, other), but we added
26964 * three explicit number rules 0, 1 and 2.
26965 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26966 * When three people view the document, no explicit number rule is found, so
26967 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26968 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26971 * Note that when you specify offsets, you must provide explicit number rules for
26972 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26973 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26974 * plural categories "one" and "other".
26976 * @param {string|expression} count The variable to be bound to.
26977 * @param {string} when The mapping between plural category to its corresponding strings.
26978 * @param {number=} offset Offset to deduct from the total number.
26981 <example module="pluralizeExample">
26982 <file name="index.html">
26984 angular.module('pluralizeExample', [])
26985 .controller('ExampleController', ['$scope', function($scope) {
26986 $scope.person1 = 'Igor';
26987 $scope.person2 = 'Misko';
26988 $scope.personCount = 1;
26991 <div ng-controller="ExampleController">
26992 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26993 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26994 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
26996 <!--- Example with simple pluralization rules for en locale --->
26998 <ng-pluralize count="personCount"
26999 when="{'0': 'Nobody is viewing.',
27000 'one': '1 person is viewing.',
27001 'other': '{} people are viewing.'}">
27002 </ng-pluralize><br>
27004 <!--- Example with offset --->
27006 <ng-pluralize count="personCount" offset=2
27007 when="{'0': 'Nobody is viewing.',
27008 '1': '{{person1}} is viewing.',
27009 '2': '{{person1}} and {{person2}} are viewing.',
27010 'one': '{{person1}}, {{person2}} and one other person are viewing.',
27011 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
27015 <file name="protractor.js" type="protractor">
27016 it('should show correct pluralized string', function() {
27017 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
27018 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27019 var countInput = element(by.model('personCount'));
27021 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
27022 expect(withOffset.getText()).toEqual('Igor is viewing.');
27024 countInput.clear();
27025 countInput.sendKeys('0');
27027 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
27028 expect(withOffset.getText()).toEqual('Nobody is viewing.');
27030 countInput.clear();
27031 countInput.sendKeys('2');
27033 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
27034 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
27036 countInput.clear();
27037 countInput.sendKeys('3');
27039 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
27040 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
27042 countInput.clear();
27043 countInput.sendKeys('4');
27045 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
27046 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
27048 it('should show data-bound names', function() {
27049 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27050 var personCount = element(by.model('personCount'));
27051 var person1 = element(by.model('person1'));
27052 var person2 = element(by.model('person2'));
27053 personCount.clear();
27054 personCount.sendKeys('4');
27056 person1.sendKeys('Di');
27058 person2.sendKeys('Vojta');
27059 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
27064 var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
27066 IS_WHEN = /^when(Minus)?(.+)$/;
27069 link: function(scope, element, attr) {
27070 var numberExp = attr.count,
27071 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
27072 offset = attr.offset || 0,
27073 whens = scope.$eval(whenExp) || {},
27075 startSymbol = $interpolate.startSymbol(),
27076 endSymbol = $interpolate.endSymbol(),
27077 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27078 watchRemover = angular.noop,
27081 forEach(attr, function(expression, attributeName) {
27082 var tmpMatch = IS_WHEN.exec(attributeName);
27084 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27085 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27088 forEach(whens, function(expression, key) {
27089 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27093 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27094 var count = parseFloat(newVal);
27095 var countIsNaN = isNaN(count);
27097 if (!countIsNaN && !(count in whens)) {
27098 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27099 // Otherwise, check it against pluralization rules in $locale service.
27100 count = $locale.pluralCat(count - offset);
27103 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27104 // In JS `NaN !== NaN`, so we have to exlicitly check.
27105 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27107 var whenExpFn = whensExpFns[count];
27108 if (isUndefined(whenExpFn)) {
27109 if (newVal != null) {
27110 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27112 watchRemover = noop;
27113 updateElementText();
27115 watchRemover = scope.$watch(whenExpFn, updateElementText);
27121 function updateElementText(newText) {
27122 element.text(newText || '');
27134 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27135 * instance gets its own scope, where the given loop variable is set to the current collection item,
27136 * and `$index` is set to the item index or key.
27138 * Special properties are exposed on the local scope of each template instance, including:
27140 * | Variable | Type | Details |
27141 * |-----------|-----------------|-----------------------------------------------------------------------------|
27142 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27143 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27144 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27145 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27146 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27147 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27149 * <div class="alert alert-info">
27150 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27151 * This may be useful when, for instance, nesting ngRepeats.
27155 * # Iterating over object properties
27157 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27161 * <div ng-repeat="(key, value) in myObj"> ... </div>
27164 * You need to be aware that the JavaScript specification does not define the order of keys
27165 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27166 * used to sort the keys alphabetically.)
27168 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27169 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27170 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27171 * and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
27173 * If this is not desired, the recommended workaround is to convert your object into an array
27174 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27175 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27176 * or implement a `$watch` on the object yourself.
27179 * # Tracking and Duplicates
27181 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27182 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27184 * * When an item is added, a new instance of the template is added to the DOM.
27185 * * When an item is removed, its template instance is removed from the DOM.
27186 * * When items are reordered, their respective templates are reordered in the DOM.
27188 * To minimize creation of DOM elements, `ngRepeat` uses a function
27189 * to "keep track" of all items in the collection and their corresponding DOM elements.
27190 * For example, if an item is added to the collection, ngRepeat will know that all other items
27191 * already have DOM elements, and will not re-render them.
27193 * The default tracking function (which tracks items by their identity) does not allow
27194 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27195 * to maintain a one-to-one mapping between collection items and DOM elements.
27197 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27198 * with your own using the `track by` expression.
27200 * For example, you may track items by the index of each item in the collection, using the
27201 * special scope property `$index`:
27203 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27208 * You may also use arbitrary expressions in `track by`, including references to custom functions
27211 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27216 * <div class="alert alert-success">
27217 * If you are working with objects that have an identifier property, you should track
27218 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27219 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27220 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27221 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27222 * `track by $index` can also provide a performance boost.
27225 * <div ng-repeat="model in collection track by model.id">
27230 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27231 * `$id` function, which tracks items by their identity:
27233 * <div ng-repeat="obj in collection track by $id(obj)">
27238 * <div class="alert alert-warning">
27239 * **Note:** `track by` must always be the last expression:
27242 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27247 * # Special repeat start and end points
27248 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27249 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27250 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on)
27251 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27253 * The example below makes use of this feature:
27255 * <header ng-repeat-start="item in items">
27256 * Header {{ item }}
27258 * <div class="body">
27261 * <footer ng-repeat-end>
27262 * Footer {{ item }}
27266 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27271 * <div class="body">
27280 * <div class="body">
27288 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27289 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27292 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27294 * **.leave** - when an item is removed from the list or when an item is filtered out
27296 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27301 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27302 * formats are currently supported:
27304 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27305 * is a scope expression giving the collection to enumerate.
27307 * For example: `album in artist.albums`.
27309 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27310 * and `expression` is the scope expression giving the collection to enumerate.
27312 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27314 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27315 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27316 * is specified, ng-repeat associates elements by identity. It is an error to have
27317 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27318 * mapped to the same DOM element, which is not possible.)
27320 * Note that the tracking expression must come last, after any filters, and the alias expression.
27322 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27323 * will be associated by item identity in the array.
27325 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27326 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27327 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27328 * element in the same way in the DOM.
27330 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27331 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27332 * property is same.
27334 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27335 * to items in conjunction with a tracking expression.
27337 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27338 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27339 * when a filter is active on the repeater, but the filtered result set is empty.
27341 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27342 * the items have been processed through the filter.
27344 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end
27345 * (and not as operator, inside an expression).
27347 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27350 * This example initializes the scope to a list of names and
27351 * then uses `ngRepeat` to display every person:
27352 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27353 <file name="index.html">
27354 <div ng-init="friends = [
27355 {name:'John', age:25, gender:'boy'},
27356 {name:'Jessie', age:30, gender:'girl'},
27357 {name:'Johanna', age:28, gender:'girl'},
27358 {name:'Joy', age:15, gender:'girl'},
27359 {name:'Mary', age:28, gender:'girl'},
27360 {name:'Peter', age:95, gender:'boy'},
27361 {name:'Sebastian', age:50, gender:'boy'},
27362 {name:'Erika', age:27, gender:'girl'},
27363 {name:'Patrick', age:40, gender:'boy'},
27364 {name:'Samantha', age:60, gender:'girl'}
27366 I have {{friends.length}} friends. They are:
27367 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27368 <ul class="example-animate-container">
27369 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27370 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27372 <li class="animate-repeat" ng-if="results.length == 0">
27373 <strong>No results found...</strong>
27378 <file name="animations.css">
27379 .example-animate-container {
27381 border:1px solid black;
27390 box-sizing:border-box;
27393 .animate-repeat.ng-move,
27394 .animate-repeat.ng-enter,
27395 .animate-repeat.ng-leave {
27396 transition:all linear 0.5s;
27399 .animate-repeat.ng-leave.ng-leave-active,
27400 .animate-repeat.ng-move,
27401 .animate-repeat.ng-enter {
27406 .animate-repeat.ng-leave,
27407 .animate-repeat.ng-move.ng-move-active,
27408 .animate-repeat.ng-enter.ng-enter-active {
27413 <file name="protractor.js" type="protractor">
27414 var friends = element.all(by.repeater('friend in friends'));
27416 it('should render initial data set', function() {
27417 expect(friends.count()).toBe(10);
27418 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27419 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27420 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27421 expect(element(by.binding('friends.length')).getText())
27422 .toMatch("I have 10 friends. They are:");
27425 it('should update repeater when filter predicate changes', function() {
27426 expect(friends.count()).toBe(10);
27428 element(by.model('q')).sendKeys('ma');
27430 expect(friends.count()).toBe(2);
27431 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27432 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27437 var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27438 var NG_REMOVED = '$$NG_REMOVED';
27439 var ngRepeatMinErr = minErr('ngRepeat');
27441 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27442 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27443 scope[valueIdentifier] = value;
27444 if (keyIdentifier) scope[keyIdentifier] = key;
27445 scope.$index = index;
27446 scope.$first = (index === 0);
27447 scope.$last = (index === (arrayLength - 1));
27448 scope.$middle = !(scope.$first || scope.$last);
27449 // jshint bitwise: false
27450 scope.$odd = !(scope.$even = (index&1) === 0);
27451 // jshint bitwise: true
27454 var getBlockStart = function(block) {
27455 return block.clone[0];
27458 var getBlockEnd = function(block) {
27459 return block.clone[block.clone.length - 1];
27465 multiElement: true,
27466 transclude: 'element',
27470 compile: function ngRepeatCompile($element, $attr) {
27471 var expression = $attr.ngRepeat;
27472 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27474 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
27477 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27481 var lhs = match[1];
27482 var rhs = match[2];
27483 var aliasAs = match[3];
27484 var trackByExp = match[4];
27486 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27489 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27492 var valueIdentifier = match[3] || match[1];
27493 var keyIdentifier = match[2];
27495 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27496 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27497 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27501 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27502 var hashFnLocals = {$id: hashKey};
27505 trackByExpGetter = $parse(trackByExp);
27507 trackByIdArrayFn = function(key, value) {
27508 return hashKey(value);
27510 trackByIdObjFn = function(key) {
27515 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27517 if (trackByExpGetter) {
27518 trackByIdExpFn = function(key, value, index) {
27519 // assign key, value, and $index to the locals so that they can be used in hash functions
27520 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27521 hashFnLocals[valueIdentifier] = value;
27522 hashFnLocals.$index = index;
27523 return trackByExpGetter($scope, hashFnLocals);
27527 // Store a list of elements from previous run. This is a hash where key is the item from the
27528 // iterator, and the value is objects with following properties.
27529 // - scope: bound scope
27530 // - element: previous element.
27531 // - index: position
27533 // We are using no-proto object so that we don't need to guard against inherited props via
27535 var lastBlockMap = createMap();
27538 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27540 previousNode = $element[0], // node that cloned nodes should be inserted after
27541 // initialized to the comment node anchor
27543 // Same as lastBlockMap but it has the current state. It will become the
27544 // lastBlockMap on the next iteration.
27545 nextBlockMap = createMap(),
27547 key, value, // key/value of iteration
27551 block, // last object information {scope, element, id}
27556 $scope[aliasAs] = collection;
27559 if (isArrayLike(collection)) {
27560 collectionKeys = collection;
27561 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27563 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27564 // if object, extract keys, in enumeration order, unsorted
27565 collectionKeys = [];
27566 for (var itemKey in collection) {
27567 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27568 collectionKeys.push(itemKey);
27573 collectionLength = collectionKeys.length;
27574 nextBlockOrder = new Array(collectionLength);
27576 // locate existing items
27577 for (index = 0; index < collectionLength; index++) {
27578 key = (collection === collectionKeys) ? index : collectionKeys[index];
27579 value = collection[key];
27580 trackById = trackByIdFn(key, value, index);
27581 if (lastBlockMap[trackById]) {
27582 // found previously seen block
27583 block = lastBlockMap[trackById];
27584 delete lastBlockMap[trackById];
27585 nextBlockMap[trackById] = block;
27586 nextBlockOrder[index] = block;
27587 } else if (nextBlockMap[trackById]) {
27588 // if collision detected. restore lastBlockMap and throw an error
27589 forEach(nextBlockOrder, function(block) {
27590 if (block && block.scope) lastBlockMap[block.id] = block;
27592 throw ngRepeatMinErr('dupes',
27593 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27594 expression, trackById, value);
27596 // new never before seen block
27597 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27598 nextBlockMap[trackById] = true;
27602 // remove leftover items
27603 for (var blockKey in lastBlockMap) {
27604 block = lastBlockMap[blockKey];
27605 elementsToRemove = getBlockNodes(block.clone);
27606 $animate.leave(elementsToRemove);
27607 if (elementsToRemove[0].parentNode) {
27608 // if the element was not removed yet because of pending animation, mark it as deleted
27609 // so that we can ignore it later
27610 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27611 elementsToRemove[index][NG_REMOVED] = true;
27614 block.scope.$destroy();
27617 // we are not using forEach for perf reasons (trying to avoid #call)
27618 for (index = 0; index < collectionLength; index++) {
27619 key = (collection === collectionKeys) ? index : collectionKeys[index];
27620 value = collection[key];
27621 block = nextBlockOrder[index];
27624 // if we have already seen this object, then we need to reuse the
27625 // associated scope/element
27627 nextNode = previousNode;
27629 // skip nodes that are already pending removal via leave animation
27631 nextNode = nextNode.nextSibling;
27632 } while (nextNode && nextNode[NG_REMOVED]);
27634 if (getBlockStart(block) != nextNode) {
27635 // existing item which got moved
27636 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27638 previousNode = getBlockEnd(block);
27639 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27641 // new item which we don't know about
27642 $transclude(function ngRepeatTransclude(clone, scope) {
27643 block.scope = scope;
27644 // http://jsperf.com/clone-vs-createcomment
27645 var endNode = ngRepeatEndComment.cloneNode(false);
27646 clone[clone.length++] = endNode;
27648 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27649 $animate.enter(clone, null, jqLite(previousNode));
27650 previousNode = endNode;
27651 // Note: We only need the first/last node of the cloned nodes.
27652 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27653 // by a directive with templateUrl when its template arrives.
27654 block.clone = clone;
27655 nextBlockMap[block.id] = block;
27656 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27660 lastBlockMap = nextBlockMap;
27667 var NG_HIDE_CLASS = 'ng-hide';
27668 var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27675 * The `ngShow` directive shows or hides the given HTML element based on the expression
27676 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27677 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27678 * in AngularJS and sets the display style to none (using an !important flag).
27679 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27682 * <!-- when $scope.myValue is truthy (element is visible) -->
27683 * <div ng-show="myValue"></div>
27685 * <!-- when $scope.myValue is falsy (element is hidden) -->
27686 * <div ng-show="myValue" class="ng-hide"></div>
27689 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27690 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27691 * from the element causing the element not to appear hidden.
27693 * ## Why is !important used?
27695 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27696 * can be easily overridden by heavier selectors. For example, something as simple
27697 * as changing the display style on a HTML list item would make hidden elements appear visible.
27698 * This also becomes a bigger issue when dealing with CSS frameworks.
27700 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27701 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27702 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27704 * ### Overriding `.ng-hide`
27706 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27707 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27708 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27709 * with extra animation classes that can be added.
27712 * .ng-hide:not(.ng-hide-animate) {
27713 * /* this is just another form of hiding an element */
27714 * display: block!important;
27715 * position: absolute;
27721 * By default you don't need to override in CSS anything and the animations will work around the display style.
27723 * ## A note about animations with `ngShow`
27725 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27726 * is true and false. This system works like the animation system present with ngClass except that
27727 * you must also include the !important flag to override the display property
27728 * so that you can perform an animation when the element is hidden during the time of the animation.
27732 * //a working example can be found at the bottom of this page
27734 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27735 * /* this is required as of 1.3x to properly
27736 * apply all styling in a show/hide animation */
27737 * transition: 0s linear all;
27740 * .my-element.ng-hide-add-active,
27741 * .my-element.ng-hide-remove-active {
27742 * /* the transition is defined in the active class */
27743 * transition: 1s linear all;
27746 * .my-element.ng-hide-add { ... }
27747 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27748 * .my-element.ng-hide-remove { ... }
27749 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27752 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27753 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27756 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27757 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27760 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27761 * then the element is shown or hidden respectively.
27764 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27765 <file name="index.html">
27766 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27769 <div class="check-element animate-show" ng-show="checked">
27770 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27775 <div class="check-element animate-show" ng-hide="checked">
27776 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27780 <file name="glyphicons.css">
27781 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27783 <file name="animations.css">
27788 border: 1px solid black;
27792 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27793 transition: all linear 0.5s;
27796 .animate-show.ng-hide {
27804 border: 1px solid black;
27808 <file name="protractor.js" type="protractor">
27809 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27810 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27812 it('should check ng-show / ng-hide', function() {
27813 expect(thumbsUp.isDisplayed()).toBeFalsy();
27814 expect(thumbsDown.isDisplayed()).toBeTruthy();
27816 element(by.model('checked')).click();
27818 expect(thumbsUp.isDisplayed()).toBeTruthy();
27819 expect(thumbsDown.isDisplayed()).toBeFalsy();
27824 var ngShowDirective = ['$animate', function($animate) {
27827 multiElement: true,
27828 link: function(scope, element, attr) {
27829 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27830 // we're adding a temporary, animation-specific class for ng-hide since this way
27831 // we can control when the element is actually displayed on screen without having
27832 // to have a global/greedy CSS selector that breaks when other animations are run.
27833 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27834 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27835 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27849 * The `ngHide` directive shows or hides the given HTML element based on the expression
27850 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27851 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27852 * in AngularJS and sets the display style to none (using an !important flag).
27853 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27856 * <!-- when $scope.myValue is truthy (element is hidden) -->
27857 * <div ng-hide="myValue" class="ng-hide"></div>
27859 * <!-- when $scope.myValue is falsy (element is visible) -->
27860 * <div ng-hide="myValue"></div>
27863 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27864 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27865 * from the element causing the element not to appear hidden.
27867 * ## Why is !important used?
27869 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27870 * can be easily overridden by heavier selectors. For example, something as simple
27871 * as changing the display style on a HTML list item would make hidden elements appear visible.
27872 * This also becomes a bigger issue when dealing with CSS frameworks.
27874 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27875 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27876 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27878 * ### Overriding `.ng-hide`
27880 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27881 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27886 * /* this is just another form of hiding an element */
27887 * display: block!important;
27888 * position: absolute;
27894 * By default you don't need to override in CSS anything and the animations will work around the display style.
27896 * ## A note about animations with `ngHide`
27898 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27899 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27900 * CSS class is added and removed for you instead of your own CSS class.
27904 * //a working example can be found at the bottom of this page
27906 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27907 * transition: 0.5s linear all;
27910 * .my-element.ng-hide-add { ... }
27911 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27912 * .my-element.ng-hide-remove { ... }
27913 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27916 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27917 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27920 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27921 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27924 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27925 * the element is shown or hidden respectively.
27928 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27929 <file name="index.html">
27930 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27933 <div class="check-element animate-hide" ng-show="checked">
27934 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27939 <div class="check-element animate-hide" ng-hide="checked">
27940 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27944 <file name="glyphicons.css">
27945 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27947 <file name="animations.css">
27949 transition: all linear 0.5s;
27953 border: 1px solid black;
27957 .animate-hide.ng-hide {
27965 border: 1px solid black;
27969 <file name="protractor.js" type="protractor">
27970 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27971 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27973 it('should check ng-show / ng-hide', function() {
27974 expect(thumbsUp.isDisplayed()).toBeFalsy();
27975 expect(thumbsDown.isDisplayed()).toBeTruthy();
27977 element(by.model('checked')).click();
27979 expect(thumbsUp.isDisplayed()).toBeTruthy();
27980 expect(thumbsDown.isDisplayed()).toBeFalsy();
27985 var ngHideDirective = ['$animate', function($animate) {
27988 multiElement: true,
27989 link: function(scope, element, attr) {
27990 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27991 // The comment inside of the ngShowDirective explains why we add and
27992 // remove a temporary class for the show/hide animation
27993 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27994 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
28007 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
28010 * @param {expression} ngStyle
28012 * {@link guide/expression Expression} which evals to an
28013 * object whose keys are CSS style names and values are corresponding values for those CSS
28016 * Since some CSS style names are not valid keys for an object, they must be quoted.
28017 * See the 'background-color' style in the example below.
28021 <file name="index.html">
28022 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
28023 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
28024 <input type="button" value="clear" ng-click="myStyle={}">
28026 <span ng-style="myStyle">Sample Text</span>
28027 <pre>myStyle={{myStyle}}</pre>
28029 <file name="style.css">
28034 <file name="protractor.js" type="protractor">
28035 var colorSpan = element(by.css('span'));
28037 it('should check ng-style', function() {
28038 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28039 element(by.css('input[value=\'set color\']')).click();
28040 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
28041 element(by.css('input[value=clear]')).click();
28042 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28047 var ngStyleDirective = ngDirective(function(scope, element, attr) {
28048 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
28049 if (oldStyles && (newStyles !== oldStyles)) {
28050 forEach(oldStyles, function(val, style) { element.css(style, '');});
28052 if (newStyles) element.css(newStyles);
28062 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
28063 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
28064 * as specified in the template.
28066 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
28067 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
28068 * matches the value obtained from the evaluated expression. In other words, you define a container element
28069 * (where you place the directive), place an expression on the **`on="..."` attribute**
28070 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
28071 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
28072 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
28073 * attribute is displayed.
28075 * <div class="alert alert-info">
28076 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28077 * as literal string values to match against.
28078 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28079 * value of the expression `$scope.someVal`.
28083 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28084 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28089 * <ANY ng-switch="expression">
28090 * <ANY ng-switch-when="matchValue1">...</ANY>
28091 * <ANY ng-switch-when="matchValue2">...</ANY>
28092 * <ANY ng-switch-default>...</ANY>
28099 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28100 * On child elements add:
28102 * * `ngSwitchWhen`: the case statement to match against. If match then this
28103 * case will be displayed. If the same match appears multiple times, all the
28104 * elements will be displayed.
28105 * * `ngSwitchDefault`: the default case when no other case match. If there
28106 * are multiple default cases, all of them will be displayed when no other
28111 <example module="switchExample" deps="angular-animate.js" animations="true">
28112 <file name="index.html">
28113 <div ng-controller="ExampleController">
28114 <select ng-model="selection" ng-options="item for item in items">
28116 <code>selection={{selection}}</code>
28118 <div class="animate-switch-container"
28119 ng-switch on="selection">
28120 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28121 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28122 <div class="animate-switch" ng-switch-default>default</div>
28126 <file name="script.js">
28127 angular.module('switchExample', ['ngAnimate'])
28128 .controller('ExampleController', ['$scope', function($scope) {
28129 $scope.items = ['settings', 'home', 'other'];
28130 $scope.selection = $scope.items[0];
28133 <file name="animations.css">
28134 .animate-switch-container {
28137 border:1px solid black;
28146 .animate-switch.ng-animate {
28147 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28156 .animate-switch.ng-leave.ng-leave-active,
28157 .animate-switch.ng-enter {
28160 .animate-switch.ng-leave,
28161 .animate-switch.ng-enter.ng-enter-active {
28165 <file name="protractor.js" type="protractor">
28166 var switchElem = element(by.css('[ng-switch]'));
28167 var select = element(by.model('selection'));
28169 it('should start in settings', function() {
28170 expect(switchElem.getText()).toMatch(/Settings Div/);
28172 it('should change to home', function() {
28173 select.all(by.css('option')).get(1).click();
28174 expect(switchElem.getText()).toMatch(/Home Span/);
28176 it('should select default', function() {
28177 select.all(by.css('option')).get(2).click();
28178 expect(switchElem.getText()).toMatch(/default/);
28183 var ngSwitchDirective = ['$animate', function($animate) {
28185 require: 'ngSwitch',
28187 // asks for $scope to fool the BC controller module
28188 controller: ['$scope', function ngSwitchController() {
28191 link: function(scope, element, attr, ngSwitchController) {
28192 var watchExpr = attr.ngSwitch || attr.on,
28193 selectedTranscludes = [],
28194 selectedElements = [],
28195 previousLeaveAnimations = [],
28196 selectedScopes = [];
28198 var spliceFactory = function(array, index) {
28199 return function() { array.splice(index, 1); };
28202 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28204 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28205 $animate.cancel(previousLeaveAnimations[i]);
28207 previousLeaveAnimations.length = 0;
28209 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28210 var selected = getBlockNodes(selectedElements[i].clone);
28211 selectedScopes[i].$destroy();
28212 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28213 promise.then(spliceFactory(previousLeaveAnimations, i));
28216 selectedElements.length = 0;
28217 selectedScopes.length = 0;
28219 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28220 forEach(selectedTranscludes, function(selectedTransclude) {
28221 selectedTransclude.transclude(function(caseElement, selectedScope) {
28222 selectedScopes.push(selectedScope);
28223 var anchor = selectedTransclude.element;
28224 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28225 var block = { clone: caseElement };
28227 selectedElements.push(block);
28228 $animate.enter(caseElement, anchor.parent(), anchor);
28237 var ngSwitchWhenDirective = ngDirective({
28238 transclude: 'element',
28240 require: '^ngSwitch',
28241 multiElement: true,
28242 link: function(scope, element, attrs, ctrl, $transclude) {
28243 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28244 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28248 var ngSwitchDefaultDirective = ngDirective({
28249 transclude: 'element',
28251 require: '^ngSwitch',
28252 multiElement: true,
28253 link: function(scope, element, attr, ctrl, $transclude) {
28254 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28255 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28261 * @name ngTransclude
28265 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28267 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28272 <example module="transcludeExample">
28273 <file name="index.html">
28275 angular.module('transcludeExample', [])
28276 .directive('pane', function(){
28280 scope: { title:'@' },
28281 template: '<div style="border: 1px solid black;">' +
28282 '<div style="background-color: gray">{{title}}</div>' +
28283 '<ng-transclude></ng-transclude>' +
28287 .controller('ExampleController', ['$scope', function($scope) {
28288 $scope.title = 'Lorem Ipsum';
28289 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28292 <div ng-controller="ExampleController">
28293 <input ng-model="title" aria-label="title"> <br/>
28294 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28295 <pane title="{{title}}">{{text}}</pane>
28298 <file name="protractor.js" type="protractor">
28299 it('should have transcluded', function() {
28300 var titleElement = element(by.model('title'));
28301 titleElement.clear();
28302 titleElement.sendKeys('TITLE');
28303 var textElement = element(by.model('text'));
28304 textElement.clear();
28305 textElement.sendKeys('TEXT');
28306 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28307 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28313 var ngTranscludeDirective = ngDirective({
28315 link: function($scope, $element, $attrs, controller, $transclude) {
28316 if (!$transclude) {
28317 throw minErr('ngTransclude')('orphan',
28318 'Illegal use of ngTransclude directive in the template! ' +
28319 'No parent directive that requires a transclusion found. ' +
28321 startingTag($element));
28324 $transclude(function(clone) {
28326 $element.append(clone);
28337 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28338 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28339 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28340 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28341 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28343 * @param {string} type Must be set to `'text/ng-template'`.
28344 * @param {string} id Cache name of the template.
28348 <file name="index.html">
28349 <script type="text/ng-template" id="/tpl.html">
28350 Content of the template.
28353 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28354 <div id="tpl-content" ng-include src="currentTpl"></div>
28356 <file name="protractor.js" type="protractor">
28357 it('should load template defined inside script tag', function() {
28358 element(by.css('#tpl-link')).click();
28359 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28364 var scriptDirective = ['$templateCache', function($templateCache) {
28368 compile: function(element, attr) {
28369 if (attr.type == 'text/ng-template') {
28370 var templateUrl = attr.id,
28371 text = element[0].text;
28373 $templateCache.put(templateUrl, text);
28379 var noopNgModelController = { $setViewValue: noop, $render: noop };
28381 function chromeHack(optionElement) {
28382 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28383 // Adding an <option selected="selected"> element to a <select required="required"> should
28384 // automatically select the new element
28385 if (optionElement[0].hasAttribute('selected')) {
28386 optionElement[0].selected = true;
28392 * @name select.SelectController
28394 * The controller for the `<select>` directive. This provides support for reading
28395 * and writing the selected value(s) of the control and also coordinates dynamically
28396 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28398 var SelectController =
28399 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28402 optionsMap = new HashMap();
28404 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28405 self.ngModelCtrl = noopNgModelController;
28407 // The "unknown" option is one that is prepended to the list if the viewValue
28408 // does not match any of the options. When it is rendered the value of the unknown
28409 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28411 // We can't just jqLite('<option>') since jqLite is not smart enough
28412 // to create it in <select> and IE barfs otherwise.
28413 self.unknownOption = jqLite(document.createElement('option'));
28414 self.renderUnknownOption = function(val) {
28415 var unknownVal = '? ' + hashKey(val) + ' ?';
28416 self.unknownOption.val(unknownVal);
28417 $element.prepend(self.unknownOption);
28418 $element.val(unknownVal);
28421 $scope.$on('$destroy', function() {
28422 // disable unknown option so that we don't do work when the whole select is being destroyed
28423 self.renderUnknownOption = noop;
28426 self.removeUnknownOption = function() {
28427 if (self.unknownOption.parent()) self.unknownOption.remove();
28431 // Read the value of the select control, the implementation of this changes depending
28432 // upon whether the select can have multiple values and whether ngOptions is at work.
28433 self.readValue = function readSingleValue() {
28434 self.removeUnknownOption();
28435 return $element.val();
28439 // Write the value to the select control, the implementation of this changes depending
28440 // upon whether the select can have multiple values and whether ngOptions is at work.
28441 self.writeValue = function writeSingleValue(value) {
28442 if (self.hasOption(value)) {
28443 self.removeUnknownOption();
28444 $element.val(value);
28445 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28447 if (value == null && self.emptyOption) {
28448 self.removeUnknownOption();
28451 self.renderUnknownOption(value);
28457 // Tell the select control that an option, with the given value, has been added
28458 self.addOption = function(value, element) {
28459 assertNotHasOwnProperty(value, '"option value"');
28460 if (value === '') {
28461 self.emptyOption = element;
28463 var count = optionsMap.get(value) || 0;
28464 optionsMap.put(value, count + 1);
28465 self.ngModelCtrl.$render();
28466 chromeHack(element);
28469 // Tell the select control that an option, with the given value, has been removed
28470 self.removeOption = function(value) {
28471 var count = optionsMap.get(value);
28474 optionsMap.remove(value);
28475 if (value === '') {
28476 self.emptyOption = undefined;
28479 optionsMap.put(value, count - 1);
28484 // Check whether the select control has an option matching the given value
28485 self.hasOption = function(value) {
28486 return !!optionsMap.get(value);
28490 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28492 if (interpolateValueFn) {
28493 // The value attribute is interpolated
28495 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28496 if (isDefined(oldVal)) {
28497 self.removeOption(oldVal);
28500 self.addOption(newVal, optionElement);
28502 } else if (interpolateTextFn) {
28503 // The text content is interpolated
28504 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28505 optionAttrs.$set('value', newVal);
28506 if (oldVal !== newVal) {
28507 self.removeOption(oldVal);
28509 self.addOption(newVal, optionElement);
28512 // The value attribute is static
28513 self.addOption(optionAttrs.value, optionElement);
28516 optionElement.on('$destroy', function() {
28517 self.removeOption(optionAttrs.value);
28518 self.ngModelCtrl.$render();
28529 * HTML `SELECT` element with angular data-binding.
28531 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28532 * between the scope and the `<select>` control (including setting default values).
28533 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28534 * {@link ngOptions `ngOptions`} directives.
28536 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28537 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28538 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28539 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28541 * <div class="alert alert-warning">
28542 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28543 * When the model needs to be bound to a non-string value, you must either explictly convert it
28544 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28545 * This is because an option element can only be bound to string values at present.
28548 * If the viewValue of `ngModel` does not match any of the options, then the control
28549 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28551 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28552 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28553 * option. See example below for demonstration.
28555 * <div class="alert alert-info">
28556 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28557 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28558 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28559 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28560 * a new scope for each repeated instance.
28564 * @param {string} ngModel Assignable angular expression to data-bind to.
28565 * @param {string=} name Property name of the form under which the control is published.
28566 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28567 * bound to the model as an array.
28568 * @param {string=} required Sets `required` validation error key if the value is not entered.
28569 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28570 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28571 * when you want to data-bind to the required attribute.
28572 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28573 * interaction with the select element.
28574 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28575 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28578 * ### Simple `select` elements with static options
28580 * <example name="static-select" module="staticSelect">
28581 * <file name="index.html">
28582 * <div ng-controller="ExampleController">
28583 * <form name="myForm">
28584 * <label for="singleSelect"> Single select: </label><br>
28585 * <select name="singleSelect" ng-model="data.singleSelect">
28586 * <option value="option-1">Option 1</option>
28587 * <option value="option-2">Option 2</option>
28590 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28591 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28592 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28593 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28594 * <option value="option-2">Option 2</option>
28596 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28597 * <tt>singleSelect = {{data.singleSelect}}</tt>
28600 * <label for="multipleSelect"> Multiple select: </label><br>
28601 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28602 * <option value="option-1">Option 1</option>
28603 * <option value="option-2">Option 2</option>
28604 * <option value="option-3">Option 3</option>
28606 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28610 * <file name="app.js">
28611 * angular.module('staticSelect', [])
28612 * .controller('ExampleController', ['$scope', function($scope) {
28614 * singleSelect: null,
28615 * multipleSelect: [],
28616 * option1: 'option-1',
28619 * $scope.forceUnknownOption = function() {
28620 * $scope.data.singleSelect = 'nonsense';
28626 * ### Using `ngRepeat` to generate `select` options
28627 * <example name="ngrepeat-select" module="ngrepeatSelect">
28628 * <file name="index.html">
28629 * <div ng-controller="ExampleController">
28630 * <form name="myForm">
28631 * <label for="repeatSelect"> Repeat select: </label>
28632 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28633 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28637 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28640 * <file name="app.js">
28641 * angular.module('ngrepeatSelect', [])
28642 * .controller('ExampleController', ['$scope', function($scope) {
28644 * repeatSelect: null,
28645 * availableOptions: [
28646 * {id: '1', name: 'Option A'},
28647 * {id: '2', name: 'Option B'},
28648 * {id: '3', name: 'Option C'}
28656 * ### Using `select` with `ngOptions` and setting a default value
28657 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28659 * <example name="select-with-default-values" module="defaultValueSelect">
28660 * <file name="index.html">
28661 * <div ng-controller="ExampleController">
28662 * <form name="myForm">
28663 * <label for="mySelect">Make a choice:</label>
28664 * <select name="mySelect" id="mySelect"
28665 * ng-options="option.name for option in data.availableOptions track by option.id"
28666 * ng-model="data.selectedOption"></select>
28669 * <tt>option = {{data.selectedOption}}</tt><br/>
28672 * <file name="app.js">
28673 * angular.module('defaultValueSelect', [])
28674 * .controller('ExampleController', ['$scope', function($scope) {
28676 * availableOptions: [
28677 * {id: '1', name: 'Option A'},
28678 * {id: '2', name: 'Option B'},
28679 * {id: '3', name: 'Option C'}
28681 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28688 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28690 * <example name="select-with-non-string-options" module="nonStringSelect">
28691 * <file name="index.html">
28692 * <select ng-model="model.id" convert-to-number>
28693 * <option value="0">Zero</option>
28694 * <option value="1">One</option>
28695 * <option value="2">Two</option>
28699 * <file name="app.js">
28700 * angular.module('nonStringSelect', [])
28701 * .run(function($rootScope) {
28702 * $rootScope.model = { id: 2 };
28704 * .directive('convertToNumber', function() {
28706 * require: 'ngModel',
28707 * link: function(scope, element, attrs, ngModel) {
28708 * ngModel.$parsers.push(function(val) {
28709 * return parseInt(val, 10);
28711 * ngModel.$formatters.push(function(val) {
28718 * <file name="protractor.js" type="protractor">
28719 * it('should initialize to model', function() {
28720 * var select = element(by.css('select'));
28721 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28727 var selectDirective = function() {
28731 require: ['select', '?ngModel'],
28732 controller: SelectController,
28739 function selectPreLink(scope, element, attr, ctrls) {
28741 // if ngModel is not defined, we don't need to do anything
28742 var ngModelCtrl = ctrls[1];
28743 if (!ngModelCtrl) return;
28745 var selectCtrl = ctrls[0];
28747 selectCtrl.ngModelCtrl = ngModelCtrl;
28749 // We delegate rendering to the `writeValue` method, which can be changed
28750 // if the select can have multiple selected values or if the options are being
28751 // generated by `ngOptions`
28752 ngModelCtrl.$render = function() {
28753 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28756 // When the selected item(s) changes we delegate getting the value of the select control
28757 // to the `readValue` method, which can be changed if the select can have multiple
28758 // selected values or if the options are being generated by `ngOptions`
28759 element.on('change', function() {
28760 scope.$apply(function() {
28761 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28765 // If the select allows multiple values then we need to modify how we read and write
28766 // values from and to the control; also what it means for the value to be empty and
28767 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28768 // doesn't trigger rendering if only an item in the array changes.
28769 if (attr.multiple) {
28771 // Read value now needs to check each option to see if it is selected
28772 selectCtrl.readValue = function readMultipleValue() {
28774 forEach(element.find('option'), function(option) {
28775 if (option.selected) {
28776 array.push(option.value);
28782 // Write value now needs to set the selected property of each matching option
28783 selectCtrl.writeValue = function writeMultipleValue(value) {
28784 var items = new HashMap(value);
28785 forEach(element.find('option'), function(option) {
28786 option.selected = isDefined(items.get(option.value));
28790 // we have to do it on each watch since ngModel watches reference, but
28791 // we need to work of an array, so we need to see if anything was inserted/removed
28792 var lastView, lastViewRef = NaN;
28793 scope.$watch(function selectMultipleWatch() {
28794 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28795 lastView = shallowCopy(ngModelCtrl.$viewValue);
28796 ngModelCtrl.$render();
28798 lastViewRef = ngModelCtrl.$viewValue;
28801 // If we are a multiple select then value is now a collection
28802 // so the meaning of $isEmpty changes
28803 ngModelCtrl.$isEmpty = function(value) {
28804 return !value || value.length === 0;
28812 // The option directive is purely designed to communicate the existence (or lack of)
28813 // of dynamically created (and destroyed) option elements to their containing select
28814 // directive via its controller.
28815 var optionDirective = ['$interpolate', function($interpolate) {
28819 compile: function(element, attr) {
28821 if (isDefined(attr.value)) {
28822 // If the value attribute is defined, check if it contains an interpolation
28823 var interpolateValueFn = $interpolate(attr.value, true);
28825 // If the value attribute is not defined then we fall back to the
28826 // text content of the option element, which may be interpolated
28827 var interpolateTextFn = $interpolate(element.text(), true);
28828 if (!interpolateTextFn) {
28829 attr.$set('value', element.text());
28833 return function(scope, element, attr) {
28835 // This is an optimization over using ^^ since we don't want to have to search
28836 // all the way to the root of the DOM for every single option element
28837 var selectCtrlName = '$selectController',
28838 parent = element.parent(),
28839 selectCtrl = parent.data(selectCtrlName) ||
28840 parent.parent().data(selectCtrlName); // in case we are in optgroup
28843 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28850 var styleDirective = valueFn({
28855 var requiredDirective = function() {
28858 require: '?ngModel',
28859 link: function(scope, elm, attr, ctrl) {
28861 attr.required = true; // force truthy in case we are on non input element
28863 ctrl.$validators.required = function(modelValue, viewValue) {
28864 return !attr.required || !ctrl.$isEmpty(viewValue);
28867 attr.$observe('required', function() {
28875 var patternDirective = function() {
28878 require: '?ngModel',
28879 link: function(scope, elm, attr, ctrl) {
28882 var regexp, patternExp = attr.ngPattern || attr.pattern;
28883 attr.$observe('pattern', function(regex) {
28884 if (isString(regex) && regex.length > 0) {
28885 regex = new RegExp('^' + regex + '$');
28888 if (regex && !regex.test) {
28889 throw minErr('ngPattern')('noregexp',
28890 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28891 regex, startingTag(elm));
28894 regexp = regex || undefined;
28898 ctrl.$validators.pattern = function(modelValue, viewValue) {
28899 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28900 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28907 var maxlengthDirective = function() {
28910 require: '?ngModel',
28911 link: function(scope, elm, attr, ctrl) {
28914 var maxlength = -1;
28915 attr.$observe('maxlength', function(value) {
28916 var intVal = toInt(value);
28917 maxlength = isNaN(intVal) ? -1 : intVal;
28920 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28921 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28927 var minlengthDirective = function() {
28930 require: '?ngModel',
28931 link: function(scope, elm, attr, ctrl) {
28935 attr.$observe('minlength', function(value) {
28936 minlength = toInt(value) || 0;
28939 ctrl.$validators.minlength = function(modelValue, viewValue) {
28940 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28946 if (window.angular.bootstrap) {
28947 //AngularJS is already loaded, so we can return here...
28948 console.log('WARNING: Tried to load angular more than once.');
28952 //try to bind to jquery now so that one can write jqLite(document).ready()
28953 //but we will rebind on bootstrap again.
28956 publishExternalAPI(angular);
28958 angular.module("ngLocale", [], ["$provide", function($provide) {
28959 var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28960 function getDecimals(n) {
28962 var i = n.indexOf('.');
28963 return (i == -1) ? 0 : n.length - i - 1;
28966 function getVF(n, opt_precision) {
28967 var v = opt_precision;
28969 if (undefined === v) {
28970 v = Math.min(getDecimals(n), 3);
28973 var base = Math.pow(10, v);
28974 var f = ((n * base) | 0) % base;
28975 return {v: v, f: f};
28978 $provide.value("$locale", {
28979 "DATETIME_FORMATS": {
29001 "FIRSTDAYOFWEEK": 6,
29043 "fullDate": "EEEE, MMMM d, y",
29044 "longDate": "MMMM d, y",
29045 "medium": "MMM d, y h:mm:ss a",
29046 "mediumDate": "MMM d, y",
29047 "mediumTime": "h:mm:ss a",
29048 "short": "M/d/yy h:mm a",
29049 "shortDate": "M/d/yy",
29050 "shortTime": "h:mm a"
29052 "NUMBER_FORMATS": {
29053 "CURRENCY_SYM": "$",
29054 "DECIMAL_SEP": ".",
29074 "negPre": "-\u00a4",
29076 "posPre": "\u00a4",
29082 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
29086 jqLite(document).ready(function() {
29087 angularInit(document, bootstrap);
29090 })(window, document);
29092 !window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
29096 /***/ function(module, exports) {
29099 * State-based routing for AngularJS
29101 * @link http://angular-ui.github.com/
29102 * @license MIT License, http://www.opensource.org/licenses/MIT
29105 /* commonjs package manager support (eg componentjs) */
29106 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
29107 module.exports = 'ui.router';
29110 (function (window, angular, undefined) {
29111 /*jshint globalstrict:true*/
29112 /*global angular:false*/
29115 var isDefined = angular.isDefined,
29116 isFunction = angular.isFunction,
29117 isString = angular.isString,
29118 isObject = angular.isObject,
29119 isArray = angular.isArray,
29120 forEach = angular.forEach,
29121 extend = angular.extend,
29122 copy = angular.copy;
29124 function inherit(parent, extra) {
29125 return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29128 function merge(dst) {
29129 forEach(arguments, function(obj) {
29131 forEach(obj, function(value, key) {
29132 if (!dst.hasOwnProperty(key)) dst[key] = value;
29140 * Finds the common ancestor path between two states.
29142 * @param {Object} first The first state.
29143 * @param {Object} second The second state.
29144 * @return {Array} Returns an array of state names in descending order, not including the root.
29146 function ancestors(first, second) {
29149 for (var n in first.path) {
29150 if (first.path[n] !== second.path[n]) break;
29151 path.push(first.path[n]);
29157 * IE8-safe wrapper for `Object.keys()`.
29159 * @param {Object} object A JavaScript object.
29160 * @return {Array} Returns the keys of the object as an array.
29162 function objectKeys(object) {
29164 return Object.keys(object);
29168 forEach(object, function(val, key) {
29175 * IE8-safe wrapper for `Array.prototype.indexOf()`.
29177 * @param {Array} array A JavaScript array.
29178 * @param {*} value A value to search the array for.
29179 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
29181 function indexOf(array, value) {
29182 if (Array.prototype.indexOf) {
29183 return array.indexOf(value, Number(arguments[2]) || 0);
29185 var len = array.length >>> 0, from = Number(arguments[2]) || 0;
29186 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
29188 if (from < 0) from += len;
29190 for (; from < len; from++) {
29191 if (from in array && array[from] === value) return from;
29197 * Merges a set of parameters with all parameters inherited between the common parents of the
29198 * current state and a given destination state.
29200 * @param {Object} currentParams The value of the current state parameters ($stateParams).
29201 * @param {Object} newParams The set of parameters which will be composited with inherited params.
29202 * @param {Object} $current Internal definition of object representing the current state.
29203 * @param {Object} $to Internal definition of object representing state to transition to.
29205 function inheritParams(currentParams, newParams, $current, $to) {
29206 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
29208 for (var i in parents) {
29209 if (!parents[i].params) continue;
29210 parentParams = objectKeys(parents[i].params);
29211 if (!parentParams.length) continue;
29213 for (var j in parentParams) {
29214 if (indexOf(inheritList, parentParams[j]) >= 0) continue;
29215 inheritList.push(parentParams[j]);
29216 inherited[parentParams[j]] = currentParams[parentParams[j]];
29219 return extend({}, inherited, newParams);
29223 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
29225 * @param {Object} a The first object.
29226 * @param {Object} b The second object.
29227 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
29228 * it defaults to the list of keys in `a`.
29229 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
29231 function equalForKeys(a, b, keys) {
29234 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
29237 for (var i=0; i<keys.length; i++) {
29239 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
29245 * Returns the subset of an object, based on a list of keys.
29247 * @param {Array} keys
29248 * @param {Object} values
29249 * @return {Boolean} Returns a subset of `values`.
29251 function filterByKeys(keys, values) {
29254 forEach(keys, function (name) {
29255 filtered[name] = values[name];
29261 // when you know that your index values will be unique, or you want last-one-in to win
29262 function indexBy(array, propName) {
29264 forEach(array, function(item) {
29265 result[item[propName]] = item;
29270 // extracted from underscore.js
29271 // Return a copy of the object only containing the whitelisted properties.
29272 function pick(obj) {
29274 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29275 forEach(keys, function(key) {
29276 if (key in obj) copy[key] = obj[key];
29281 // extracted from underscore.js
29282 // Return a copy of the object omitting the blacklisted properties.
29283 function omit(obj) {
29285 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29286 for (var key in obj) {
29287 if (indexOf(keys, key) == -1) copy[key] = obj[key];
29292 function pluck(collection, key) {
29293 var result = isArray(collection) ? [] : {};
29295 forEach(collection, function(val, i) {
29296 result[i] = isFunction(key) ? key(val) : val[key];
29301 function filter(collection, callback) {
29302 var array = isArray(collection);
29303 var result = array ? [] : {};
29304 forEach(collection, function(val, i) {
29305 if (callback(val, i)) {
29306 result[array ? result.length : i] = val;
29312 function map(collection, callback) {
29313 var result = isArray(collection) ? [] : {};
29315 forEach(collection, function(val, i) {
29316 result[i] = callback(val, i);
29323 * @name ui.router.util
29326 * # ui.router.util sub-module
29328 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29329 * in your angular app (use {@link ui.router} module instead).
29332 angular.module('ui.router.util', ['ng']);
29336 * @name ui.router.router
29338 * @requires ui.router.util
29341 * # ui.router.router sub-module
29343 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29344 * in your angular app (use {@link ui.router} module instead).
29346 angular.module('ui.router.router', ['ui.router.util']);
29350 * @name ui.router.state
29352 * @requires ui.router.router
29353 * @requires ui.router.util
29356 * # ui.router.state sub-module
29358 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
29359 * in your angular app (use {@link ui.router} module instead).
29362 angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
29368 * @requires ui.router.state
29373 * ## The main module for ui.router
29374 * There are several sub-modules included with the ui.router module, however only this module is needed
29375 * as a dependency within your angular app. The other modules are for organization purposes.
29378 * * ui.router - the main "umbrella" module
29379 * * ui.router.router -
29381 * *You'll need to include **only** this module as the dependency within your angular app.*
29385 * <html ng-app="myApp">
29387 * <script src="js/angular.js"></script>
29388 * <!-- Include the ui-router script -->
29389 * <script src="js/angular-ui-router.min.js"></script>
29391 * // ...and add 'ui.router' as a dependency
29392 * var myApp = angular.module('myApp', ['ui.router']);
29400 angular.module('ui.router', ['ui.router.state']);
29402 angular.module('ui.router.compat', ['ui.router']);
29406 * @name ui.router.util.$resolve
29409 * @requires $injector
29412 * Manages resolution of (acyclic) graphs of promises.
29414 $Resolve.$inject = ['$q', '$injector'];
29415 function $Resolve( $q, $injector) {
29417 var VISIT_IN_PROGRESS = 1,
29420 NO_DEPENDENCIES = [],
29421 NO_LOCALS = NOTHING,
29422 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
29427 * @name ui.router.util.$resolve#study
29428 * @methodOf ui.router.util.$resolve
29431 * Studies a set of invocables that are likely to be used multiple times.
29433 * $resolve.study(invocables)(locals, parent, self)
29437 * $resolve.resolve(invocables, locals, parent, self)
29439 * but the former is more efficient (in fact `resolve` just calls `study`
29442 * @param {object} invocables Invocable objects
29443 * @return {function} a function to pass in locals, parent and self
29445 this.study = function (invocables) {
29446 if (!isObject(invocables)) throw new Error("'invocables' must be an object");
29447 var invocableKeys = objectKeys(invocables || {});
29449 // Perform a topological sort of invocables to build an ordered plan
29450 var plan = [], cycle = [], visited = {};
29451 function visit(value, key) {
29452 if (visited[key] === VISIT_DONE) return;
29455 if (visited[key] === VISIT_IN_PROGRESS) {
29456 cycle.splice(0, indexOf(cycle, key));
29457 throw new Error("Cyclic dependency: " + cycle.join(" -> "));
29459 visited[key] = VISIT_IN_PROGRESS;
29461 if (isString(value)) {
29462 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
29464 var params = $injector.annotate(value);
29465 forEach(params, function (param) {
29466 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
29468 plan.push(key, value, params);
29472 visited[key] = VISIT_DONE;
29474 forEach(invocables, visit);
29475 invocables = cycle = visited = null; // plan is all that's required
29477 function isResolve(value) {
29478 return isObject(value) && value.then && value.$$promises;
29481 return function (locals, parent, self) {
29482 if (isResolve(locals) && self === undefined) {
29483 self = parent; parent = locals; locals = null;
29485 if (!locals) locals = NO_LOCALS;
29486 else if (!isObject(locals)) {
29487 throw new Error("'locals' must be an object");
29489 if (!parent) parent = NO_PARENT;
29490 else if (!isResolve(parent)) {
29491 throw new Error("'parent' must be a promise returned by $resolve.resolve()");
29494 // To complete the overall resolution, we have to wait for the parent
29495 // promise and for the promise for each invokable in our plan.
29496 var resolution = $q.defer(),
29497 result = resolution.promise,
29498 promises = result.$$promises = {},
29499 values = extend({}, locals),
29500 wait = 1 + plan.length/3,
29504 // Merge parent values we haven't got yet and publish our own $$values
29506 if (!merged) merge(values, parent.$$values);
29507 result.$$values = values;
29508 result.$$promises = result.$$promises || true; // keep for isResolve()
29509 delete result.$$inheritedValues;
29510 resolution.resolve(values);
29514 function fail(reason) {
29515 result.$$failure = reason;
29516 resolution.reject(reason);
29519 // Short-circuit if parent has already failed
29520 if (isDefined(parent.$$failure)) {
29521 fail(parent.$$failure);
29525 if (parent.$$inheritedValues) {
29526 merge(values, omit(parent.$$inheritedValues, invocableKeys));
29529 // Merge parent values if the parent has already resolved, or merge
29530 // parent promises and wait if the parent resolve is still in progress.
29531 extend(promises, parent.$$promises);
29532 if (parent.$$values) {
29533 merged = merge(values, omit(parent.$$values, invocableKeys));
29534 result.$$inheritedValues = omit(parent.$$values, invocableKeys);
29537 if (parent.$$inheritedValues) {
29538 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
29540 parent.then(done, fail);
29543 // Process each invocable in the plan, but ignore any where a local of the same name exists.
29544 for (var i=0, ii=plan.length; i<ii; i+=3) {
29545 if (locals.hasOwnProperty(plan[i])) done();
29546 else invoke(plan[i], plan[i+1], plan[i+2]);
29549 function invoke(key, invocable, params) {
29550 // Create a deferred for this invocation. Failures will propagate to the resolution as well.
29551 var invocation = $q.defer(), waitParams = 0;
29552 function onfailure(reason) {
29553 invocation.reject(reason);
29556 // Wait for any parameter that we have a promise for (either from parent or from this
29557 // resolve; in that case study() will have made sure it's ordered before us in the plan).
29558 forEach(params, function (dep) {
29559 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
29561 promises[dep].then(function (result) {
29562 values[dep] = result;
29563 if (!(--waitParams)) proceed();
29567 if (!waitParams) proceed();
29568 function proceed() {
29569 if (isDefined(result.$$failure)) return;
29571 invocation.resolve($injector.invoke(invocable, self, values));
29572 invocation.promise.then(function (result) {
29573 values[key] = result;
29580 // Publish promise synchronously; invocations further down in the plan may depend on it.
29581 promises[key] = invocation.promise;
29590 * @name ui.router.util.$resolve#resolve
29591 * @methodOf ui.router.util.$resolve
29594 * Resolves a set of invocables. An invocable is a function to be invoked via
29595 * `$injector.invoke()`, and can have an arbitrary number of dependencies.
29596 * An invocable can either return a value directly,
29597 * or a `$q` promise. If a promise is returned it will be resolved and the
29598 * resulting value will be used instead. Dependencies of invocables are resolved
29599 * (in this order of precedence)
29601 * - from the specified `locals`
29602 * - from another invocable that is part of this `$resolve` call
29603 * - from an invocable that is inherited from a `parent` call to `$resolve`
29605 * - from any ancestor `$resolve` of that parent).
29607 * The return value of `$resolve` is a promise for an object that contains
29608 * (in this order of precedence)
29610 * - any `locals` (if specified)
29611 * - the resolved return values of all injectables
29612 * - any values inherited from a `parent` call to `$resolve` (if specified)
29614 * The promise will resolve after the `parent` promise (if any) and all promises
29615 * returned by injectables have been resolved. If any invocable
29616 * (or `$injector.invoke`) throws an exception, or if a promise returned by an
29617 * invocable is rejected, the `$resolve` promise is immediately rejected with the
29618 * same error. A rejection of a `parent` promise (if specified) will likewise be
29619 * propagated immediately. Once the `$resolve` promise has been rejected, no
29620 * further invocables will be called.
29622 * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
29623 * to throw an error. As a special case, an injectable can depend on a parameter
29624 * with the same name as the injectable, which will be fulfilled from the `parent`
29625 * injectable of the same name. This allows inherited values to be decorated.
29626 * Note that in this case any other injectable in the same `$resolve` with the same
29627 * dependency would see the decorated value, not the inherited value.
29629 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
29630 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
29633 * Invocables are invoked eagerly as soon as all dependencies are available.
29634 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
29636 * As a special case, an invocable can be a string, in which case it is taken to
29637 * be a service name to be passed to `$injector.get()`. This is supported primarily
29638 * for backwards-compatibility with the `resolve` property of `$routeProvider`
29641 * @param {object} invocables functions to invoke or
29642 * `$injector` services to fetch.
29643 * @param {object} locals values to make available to the injectables
29644 * @param {object} parent a promise returned by another call to `$resolve`.
29645 * @param {object} self the `this` for the invoked methods
29646 * @return {object} Promise for an object that contains the resolved return value
29647 * of all invocables, as well as any inherited and local values.
29649 this.resolve = function (invocables, locals, parent, self) {
29650 return this.study(invocables)(locals, parent, self);
29654 angular.module('ui.router.util').service('$resolve', $Resolve);
29659 * @name ui.router.util.$templateFactory
29662 * @requires $templateCache
29663 * @requires $injector
29666 * Service. Manages loading of templates.
29668 $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
29669 function $TemplateFactory( $http, $templateCache, $injector) {
29673 * @name ui.router.util.$templateFactory#fromConfig
29674 * @methodOf ui.router.util.$templateFactory
29677 * Creates a template from a configuration object.
29679 * @param {object} config Configuration object for which to load a template.
29680 * The following properties are search in the specified order, and the first one
29681 * that is defined is used to create the template:
29683 * @param {string|object} config.template html string template or function to
29684 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
29685 * @param {string|object} config.templateUrl url to load or a function returning
29686 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
29687 * @param {Function} config.templateProvider function to invoke via
29688 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
29689 * @param {object} params Parameters to pass to the template function.
29690 * @param {object} locals Locals to pass to `invoke` if the template is loaded
29691 * via a `templateProvider`. Defaults to `{ params: params }`.
29693 * @return {string|object} The template html as a string, or a promise for
29694 * that string,or `null` if no template is configured.
29696 this.fromConfig = function (config, params, locals) {
29698 isDefined(config.template) ? this.fromString(config.template, params) :
29699 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
29700 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
29707 * @name ui.router.util.$templateFactory#fromString
29708 * @methodOf ui.router.util.$templateFactory
29711 * Creates a template from a string or a function returning a string.
29713 * @param {string|object} template html template as a string or function that
29714 * returns an html template as a string.
29715 * @param {object} params Parameters to pass to the template function.
29717 * @return {string|object} The template html as a string, or a promise for that
29720 this.fromString = function (template, params) {
29721 return isFunction(template) ? template(params) : template;
29726 * @name ui.router.util.$templateFactory#fromUrl
29727 * @methodOf ui.router.util.$templateFactory
29730 * Loads a template from the a URL via `$http` and `$templateCache`.
29732 * @param {string|Function} url url of the template to load, or a function
29733 * that returns a url.
29734 * @param {Object} params Parameters to pass to the url function.
29735 * @return {string|Promise.<string>} The template html as a string, or a promise
29738 this.fromUrl = function (url, params) {
29739 if (isFunction(url)) url = url(params);
29740 if (url == null) return null;
29742 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
29743 .then(function(response) { return response.data; });
29748 * @name ui.router.util.$templateFactory#fromProvider
29749 * @methodOf ui.router.util.$templateFactory
29752 * Creates a template by invoking an injectable provider function.
29754 * @param {Function} provider Function to invoke via `$injector.invoke`
29755 * @param {Object} params Parameters for the template.
29756 * @param {Object} locals Locals to pass to `invoke`. Defaults to
29757 * `{ params: params }`.
29758 * @return {string|Promise.<string>} The template html as a string, or a promise
29761 this.fromProvider = function (provider, params, locals) {
29762 return $injector.invoke(provider, null, locals || { params: params });
29766 angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
29768 var $$UMFP; // reference to $UrlMatcherFactoryProvider
29772 * @name ui.router.util.type:UrlMatcher
29775 * Matches URLs against patterns and extracts named parameters from the path or the search
29776 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
29777 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
29778 * do not influence whether or not a URL is matched, but their values are passed through into
29779 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
29781 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
29782 * syntax, which optionally allows a regular expression for the parameter to be specified:
29784 * * `':'` name - colon placeholder
29785 * * `'*'` name - catch-all placeholder
29786 * * `'{' name '}'` - curly placeholder
29787 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
29788 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
29790 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
29791 * must be unique within the pattern (across both path and search parameters). For colon
29792 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
29793 * number of characters other than '/'. For catch-all placeholders the path parameter matches
29794 * any number of characters.
29798 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
29799 * trailing slashes, and patterns have to match the entire path, not just a prefix.
29800 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
29801 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
29802 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
29803 * * `'/user/{id:[^/]*}'` - Same as the previous example.
29804 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
29805 * parameter consists of 1 to 8 hex digits.
29806 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
29807 * path into the parameter 'path'.
29808 * * `'/files/*path'` - ditto.
29809 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
29810 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
29812 * @param {string} pattern The pattern to compile into a matcher.
29813 * @param {Object} config A configuration object hash:
29814 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
29815 * an existing UrlMatcher
29817 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
29818 * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
29820 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
29821 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
29822 * non-null) will start with this prefix.
29824 * @property {string} source The pattern that was passed into the constructor
29826 * @property {string} sourcePath The path portion of the source property
29828 * @property {string} sourceSearch The search portion of the source property
29830 * @property {string} regex The constructed regex that will be used to match against the url when
29831 * it is time to determine which url will match.
29833 * @returns {Object} New `UrlMatcher` object
29835 function UrlMatcher(pattern, config, parentMatcher) {
29836 config = extend({ params: {} }, isObject(config) ? config : {});
29838 // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
29842 // '{' name ':' regexp '}'
29843 // The regular expression is somewhat complicated due to the need to allow curly braces
29844 // inside the regular expression. The placeholder regexp breaks down as follows:
29845 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
29846 // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
29847 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
29848 // [^{}\\]+ - anything other than curly braces or backslash
29849 // \\. - a backslash escape
29850 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
29851 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29852 searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29853 compiled = '^', last = 0, m,
29854 segments = this.segments = [],
29855 parentParams = parentMatcher ? parentMatcher.params : {},
29856 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
29859 function addParameter(id, type, config, location) {
29860 paramNames.push(id);
29861 if (parentParams[id]) return parentParams[id];
29862 if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
29863 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
29864 params[id] = new $$UMFP.Param(id, type, config, location);
29868 function quoteRegExp(string, pattern, squash, optional) {
29869 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
29870 if (!pattern) return result;
29872 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
29873 case true: surroundPattern = ['?(', ')?']; break;
29874 default: surroundPattern = ['(' + squash + "|", ')?']; break;
29876 return result + surroundPattern[0] + pattern + surroundPattern[1];
29879 this.source = pattern;
29881 // Split into static segments separated by path parameter placeholders.
29882 // The number of segments is always 1 more than the number of parameters.
29883 function matchDetails(m, isSearch) {
29884 var id, regexp, segment, type, cfg, arrayMode;
29885 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
29886 cfg = config.params[id];
29887 segment = pattern.substring(last, m.index);
29888 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
29889 type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
29891 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
29895 var p, param, segment;
29896 while ((m = placeholder.exec(pattern))) {
29897 p = matchDetails(m, false);
29898 if (p.segment.indexOf('?') >= 0) break; // we're into the search part
29900 param = addParameter(p.id, p.type, p.cfg, "path");
29901 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
29902 segments.push(p.segment);
29903 last = placeholder.lastIndex;
29905 segment = pattern.substring(last);
29907 // Find any search parameter names and remove them from the last segment
29908 var i = segment.indexOf('?');
29911 var search = this.sourceSearch = segment.substring(i);
29912 segment = segment.substring(0, i);
29913 this.sourcePath = pattern.substring(0, last + i);
29915 if (search.length > 0) {
29917 while ((m = searchPlaceholder.exec(search))) {
29918 p = matchDetails(m, true);
29919 param = addParameter(p.id, p.type, p.cfg, "search");
29920 last = placeholder.lastIndex;
29925 this.sourcePath = pattern;
29926 this.sourceSearch = '';
29929 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
29930 segments.push(segment);
29932 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
29933 this.prefix = segments[0];
29934 this.$$paramNames = paramNames;
29939 * @name ui.router.util.type:UrlMatcher#concat
29940 * @methodOf ui.router.util.type:UrlMatcher
29943 * Returns a new matcher for a pattern constructed by appending the path part and adding the
29944 * search parameters of the specified pattern to this pattern. The current pattern is not
29945 * modified. This can be understood as creating a pattern for URLs that are relative to (or
29946 * suffixes of) the current pattern.
29949 * The following two matchers are equivalent:
29951 * new UrlMatcher('/user/{id}?q').concat('/details?date');
29952 * new UrlMatcher('/user/{id}/details?q&date');
29955 * @param {string} pattern The pattern to append.
29956 * @param {Object} config An object hash of the configuration for the matcher.
29957 * @returns {UrlMatcher} A matcher for the concatenated pattern.
29959 UrlMatcher.prototype.concat = function (pattern, config) {
29960 // Because order of search parameters is irrelevant, we can add our own search
29961 // parameters to the end of the new pattern. Parse the new pattern by itself
29962 // and then join the bits together, but it's much easier to do this on a string level.
29963 var defaultConfig = {
29964 caseInsensitive: $$UMFP.caseInsensitive(),
29965 strict: $$UMFP.strictMode(),
29966 squash: $$UMFP.defaultSquashPolicy()
29968 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
29971 UrlMatcher.prototype.toString = function () {
29972 return this.source;
29977 * @name ui.router.util.type:UrlMatcher#exec
29978 * @methodOf ui.router.util.type:UrlMatcher
29981 * Tests the specified path against this matcher, and returns an object containing the captured
29982 * parameter values, or null if the path does not match. The returned object contains the values
29983 * of any search parameters that are mentioned in the pattern, but their value may be null if
29984 * they are not present in `searchParams`. This means that search parameters are always treated
29989 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
29990 * x: '1', q: 'hello'
29992 * // returns { id: 'bob', q: 'hello', r: null }
29995 * @param {string} path The URL path to match, e.g. `$location.path()`.
29996 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
29997 * @returns {Object} The captured parameter values.
29999 UrlMatcher.prototype.exec = function (path, searchParams) {
30000 var m = this.regexp.exec(path);
30001 if (!m) return null;
30002 searchParams = searchParams || {};
30004 var paramNames = this.parameters(), nTotal = paramNames.length,
30005 nPath = this.segments.length - 1,
30006 values = {}, i, j, cfg, paramName;
30008 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
30010 function decodePathArray(string) {
30011 function reverseString(str) { return str.split("").reverse().join(""); }
30012 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
30014 var split = reverseString(string).split(/-(?!\\)/);
30015 var allReversed = map(split, reverseString);
30016 return map(allReversed, unquoteDashes).reverse();
30019 for (i = 0; i < nPath; i++) {
30020 paramName = paramNames[i];
30021 var param = this.params[paramName];
30022 var paramVal = m[i+1];
30023 // if the param value matches a pre-replace pair, replace the value before decoding.
30024 for (j = 0; j < param.replace; j++) {
30025 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30027 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
30028 values[paramName] = param.value(paramVal);
30030 for (/**/; i < nTotal; i++) {
30031 paramName = paramNames[i];
30032 values[paramName] = this.params[paramName].value(searchParams[paramName]);
30040 * @name ui.router.util.type:UrlMatcher#parameters
30041 * @methodOf ui.router.util.type:UrlMatcher
30044 * Returns the names of all path and search parameters of this pattern in an unspecified order.
30046 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
30047 * pattern has no parameters, an empty array is returned.
30049 UrlMatcher.prototype.parameters = function (param) {
30050 if (!isDefined(param)) return this.$$paramNames;
30051 return this.params[param] || null;
30056 * @name ui.router.util.type:UrlMatcher#validate
30057 * @methodOf ui.router.util.type:UrlMatcher
30060 * Checks an object hash of parameters to validate their correctness according to the parameter
30061 * types of this `UrlMatcher`.
30063 * @param {Object} params The object hash of parameters to validate.
30064 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
30066 UrlMatcher.prototype.validates = function (params) {
30067 return this.params.$$validates(params);
30072 * @name ui.router.util.type:UrlMatcher#format
30073 * @methodOf ui.router.util.type:UrlMatcher
30076 * Creates a URL that matches this pattern by substituting the specified values
30077 * for the path and search parameters. Null values for path parameters are
30078 * treated as empty strings.
30082 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
30083 * // returns '/user/bob?q=yes'
30086 * @param {Object} values the values to substitute for the parameters in this pattern.
30087 * @returns {string} the formatted URL (path and optionally search part).
30089 UrlMatcher.prototype.format = function (values) {
30090 values = values || {};
30091 var segments = this.segments, params = this.parameters(), paramset = this.params;
30092 if (!this.validates(values)) return null;
30094 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
30096 function encodeDashes(str) { // Replace dashes with encoded "\-"
30097 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
30100 for (i = 0; i < nTotal; i++) {
30101 var isPathParam = i < nPath;
30102 var name = params[i], param = paramset[name], value = param.value(values[name]);
30103 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
30104 var squash = isDefaultValue ? param.squash : false;
30105 var encoded = param.type.encode(value);
30108 var nextSegment = segments[i + 1];
30109 if (squash === false) {
30110 if (encoded != null) {
30111 if (isArray(encoded)) {
30112 result += map(encoded, encodeDashes).join("-");
30114 result += encodeURIComponent(encoded);
30117 result += nextSegment;
30118 } else if (squash === true) {
30119 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
30120 result += nextSegment.match(capture)[1];
30121 } else if (isString(squash)) {
30122 result += squash + nextSegment;
30125 if (encoded == null || (isDefaultValue && squash !== false)) continue;
30126 if (!isArray(encoded)) encoded = [ encoded ];
30127 encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
30128 result += (search ? '&' : '?') + (name + '=' + encoded);
30138 * @name ui.router.util.type:Type
30141 * Implements an interface to define custom parameter types that can be decoded from and encoded to
30142 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
30143 * objects when matching or formatting URLs, or comparing or validating parameter values.
30145 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
30146 * information on registering custom types.
30148 * @param {Object} config A configuration object which contains the custom type definition. The object's
30149 * properties will override the default methods and/or pattern in `Type`'s public interface.
30153 * decode: function(val) { return parseInt(val, 10); },
30154 * encode: function(val) { return val && val.toString(); },
30155 * equals: function(a, b) { return this.is(a) && a === b; },
30156 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
30161 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
30162 * coming from a substring of a URL.
30164 * @returns {Object} Returns a new `Type` object.
30166 function Type(config) {
30167 extend(this, config);
30172 * @name ui.router.util.type:Type#is
30173 * @methodOf ui.router.util.type:Type
30176 * Detects whether a value is of a particular type. Accepts a native (decoded) value
30177 * and determines whether it matches the current `Type` object.
30179 * @param {*} val The value to check.
30180 * @param {string} key Optional. If the type check is happening in the context of a specific
30181 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
30182 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
30183 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
30185 Type.prototype.is = function(val, key) {
30191 * @name ui.router.util.type:Type#encode
30192 * @methodOf ui.router.util.type:Type
30195 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
30196 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
30197 * only needs to be a representation of `val` that has been coerced to a string.
30199 * @param {*} val The value to encode.
30200 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30201 * meta-programming of `Type` objects.
30202 * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
30204 Type.prototype.encode = function(val, key) {
30210 * @name ui.router.util.type:Type#decode
30211 * @methodOf ui.router.util.type:Type
30214 * Converts a parameter value (from URL string or transition param) to a custom/native value.
30216 * @param {string} val The URL parameter value to decode.
30217 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30218 * meta-programming of `Type` objects.
30219 * @returns {*} Returns a custom representation of the URL parameter value.
30221 Type.prototype.decode = function(val, key) {
30227 * @name ui.router.util.type:Type#equals
30228 * @methodOf ui.router.util.type:Type
30231 * Determines whether two decoded values are equivalent.
30233 * @param {*} a A value to compare against.
30234 * @param {*} b A value to compare against.
30235 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
30237 Type.prototype.equals = function(a, b) {
30241 Type.prototype.$subPattern = function() {
30242 var sub = this.pattern.toString();
30243 return sub.substr(1, sub.length - 2);
30246 Type.prototype.pattern = /.*/;
30248 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
30250 /** Given an encoded string, or a decoded object, returns a decoded object */
30251 Type.prototype.$normalize = function(val) {
30252 return this.is(val) ? val : this.decode(val);
30256 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
30258 * - urlmatcher pattern "/path?{queryParam[]:int}"
30259 * - url: "/path?queryParam=1&queryParam=2
30260 * - $stateParams.queryParam will be [1, 2]
30261 * if `mode` is "auto", then
30262 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
30263 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
30265 Type.prototype.$asArray = function(mode, isSearch) {
30266 if (!mode) return this;
30267 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
30269 function ArrayType(type, mode) {
30270 function bindTo(type, callbackName) {
30271 return function() {
30272 return type[callbackName].apply(type, arguments);
30276 // Wrap non-array value as array
30277 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
30278 // Unwrap array value for "auto" mode. Return undefined for empty array.
30279 function arrayUnwrap(val) {
30280 switch(val.length) {
30281 case 0: return undefined;
30282 case 1: return mode === "auto" ? val[0] : val;
30283 default: return val;
30286 function falsey(val) { return !val; }
30288 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
30289 function arrayHandler(callback, allTruthyMode) {
30290 return function handleArray(val) {
30291 val = arrayWrap(val);
30292 var result = map(val, callback);
30293 if (allTruthyMode === true)
30294 return filter(result, falsey).length === 0;
30295 return arrayUnwrap(result);
30299 // Wraps type (.equals) functions to operate on each value of an array
30300 function arrayEqualsHandler(callback) {
30301 return function handleArray(val1, val2) {
30302 var left = arrayWrap(val1), right = arrayWrap(val2);
30303 if (left.length !== right.length) return false;
30304 for (var i = 0; i < left.length; i++) {
30305 if (!callback(left[i], right[i])) return false;
30311 this.encode = arrayHandler(bindTo(type, 'encode'));
30312 this.decode = arrayHandler(bindTo(type, 'decode'));
30313 this.is = arrayHandler(bindTo(type, 'is'), true);
30314 this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
30315 this.pattern = type.pattern;
30316 this.$normalize = arrayHandler(bindTo(type, '$normalize'));
30317 this.name = type.name;
30318 this.$arrayMode = mode;
30321 return new ArrayType(this, mode);
30328 * @name ui.router.util.$urlMatcherFactory
30331 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
30332 * is also available to providers under the name `$urlMatcherFactoryProvider`.
30334 function $UrlMatcherFactory() {
30337 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
30339 function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
30340 function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
30342 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
30344 encode: valToString,
30345 decode: valFromString,
30346 // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
30347 // In 0.2.x, string params are optional by default for backwards compat
30348 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
30352 encode: valToString,
30353 decode: function(val) { return parseInt(val, 10); },
30354 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
30358 encode: function(val) { return val ? 1 : 0; },
30359 decode: function(val) { return parseInt(val, 10) !== 0; },
30360 is: function(val) { return val === true || val === false; },
30364 encode: function (val) {
30367 return [ val.getFullYear(),
30368 ('0' + (val.getMonth() + 1)).slice(-2),
30369 ('0' + val.getDate()).slice(-2)
30372 decode: function (val) {
30373 if (this.is(val)) return val;
30374 var match = this.capture.exec(val);
30375 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
30377 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
30378 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
30379 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
30380 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
30383 encode: angular.toJson,
30384 decode: angular.fromJson,
30385 is: angular.isObject,
30386 equals: angular.equals,
30389 any: { // does not encode/decode
30390 encode: angular.identity,
30391 decode: angular.identity,
30392 equals: angular.equals,
30397 function getDefaultConfig() {
30399 strict: isStrictMode,
30400 caseInsensitive: isCaseInsensitive
30404 function isInjectable(value) {
30405 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
30409 * [Internal] Get the default value of a parameter, which may be an injectable function.
30411 $UrlMatcherFactory.$$getDefaultValue = function(config) {
30412 if (!isInjectable(config.value)) return config.value;
30413 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30414 return injector.invoke(config.value);
30419 * @name ui.router.util.$urlMatcherFactory#caseInsensitive
30420 * @methodOf ui.router.util.$urlMatcherFactory
30423 * Defines whether URL matching should be case sensitive (the default behavior), or not.
30425 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
30426 * @returns {boolean} the current value of caseInsensitive
30428 this.caseInsensitive = function(value) {
30429 if (isDefined(value))
30430 isCaseInsensitive = value;
30431 return isCaseInsensitive;
30436 * @name ui.router.util.$urlMatcherFactory#strictMode
30437 * @methodOf ui.router.util.$urlMatcherFactory
30440 * Defines whether URLs should match trailing slashes, or not (the default behavior).
30442 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
30443 * @returns {boolean} the current value of strictMode
30445 this.strictMode = function(value) {
30446 if (isDefined(value))
30447 isStrictMode = value;
30448 return isStrictMode;
30453 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
30454 * @methodOf ui.router.util.$urlMatcherFactory
30457 * Sets the default behavior when generating or matching URLs with default parameter values.
30459 * @param {string} value A string that defines the default parameter URL squashing behavior.
30460 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
30461 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
30462 * parameter is surrounded by slashes, squash (remove) one slash from the URL
30463 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
30464 * the parameter value from the URL and replace it with this string.
30466 this.defaultSquashPolicy = function(value) {
30467 if (!isDefined(value)) return defaultSquashPolicy;
30468 if (value !== true && value !== false && !isString(value))
30469 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
30470 defaultSquashPolicy = value;
30476 * @name ui.router.util.$urlMatcherFactory#compile
30477 * @methodOf ui.router.util.$urlMatcherFactory
30480 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
30482 * @param {string} pattern The URL pattern.
30483 * @param {Object} config The config object hash.
30484 * @returns {UrlMatcher} The UrlMatcher.
30486 this.compile = function (pattern, config) {
30487 return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
30492 * @name ui.router.util.$urlMatcherFactory#isMatcher
30493 * @methodOf ui.router.util.$urlMatcherFactory
30496 * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
30498 * @param {Object} object The object to perform the type check against.
30499 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
30500 * implementing all the same methods.
30502 this.isMatcher = function (o) {
30503 if (!isObject(o)) return false;
30506 forEach(UrlMatcher.prototype, function(val, name) {
30507 if (isFunction(val)) {
30508 result = result && (isDefined(o[name]) && isFunction(o[name]));
30516 * @name ui.router.util.$urlMatcherFactory#type
30517 * @methodOf ui.router.util.$urlMatcherFactory
30520 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
30521 * generate URLs with typed parameters.
30523 * @param {string} name The type name.
30524 * @param {Object|Function} definition The type definition. See
30525 * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30526 * @param {Object|Function} definitionFn (optional) A function that is injected before the app
30527 * runtime starts. The result of this function is merged into the existing `definition`.
30528 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30530 * @returns {Object} Returns `$urlMatcherFactoryProvider`.
30533 * This is a simple example of a custom type that encodes and decodes items from an
30534 * array, using the array index as the URL-encoded value:
30537 * var list = ['John', 'Paul', 'George', 'Ringo'];
30539 * $urlMatcherFactoryProvider.type('listItem', {
30540 * encode: function(item) {
30541 * // Represent the list item in the URL using its corresponding index
30542 * return list.indexOf(item);
30544 * decode: function(item) {
30545 * // Look up the list item by index
30546 * return list[parseInt(item, 10)];
30548 * is: function(item) {
30549 * // Ensure the item is valid by checking to see that it appears
30551 * return list.indexOf(item) > -1;
30555 * $stateProvider.state('list', {
30556 * url: "/list/{item:listItem}",
30557 * controller: function($scope, $stateParams) {
30558 * console.log($stateParams.item);
30564 * // Changes URL to '/list/3', logs "Ringo" to the console
30565 * $state.go('list', { item: "Ringo" });
30568 * This is a more complex example of a type that relies on dependency injection to
30569 * interact with services, and uses the parameter name from the URL to infer how to
30570 * handle encoding and decoding parameter values:
30573 * // Defines a custom type that gets a value from a service,
30574 * // where each service gets different types of values from
30575 * // a backend API:
30576 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
30578 * // Matches up services to URL parameter names
30585 * encode: function(object) {
30586 * // Represent the object in the URL using its unique ID
30587 * return object.id;
30589 * decode: function(value, key) {
30590 * // Look up the object by ID, using the parameter
30591 * // name (key) to call the correct service
30592 * return services[key].findById(value);
30594 * is: function(object, key) {
30595 * // Check that object is a valid dbObject
30596 * return angular.isObject(object) && object.id && services[key];
30598 * equals: function(a, b) {
30599 * // Check the equality of decoded objects by comparing
30600 * // their unique IDs
30601 * return a.id === b.id;
30606 * // In a config() block, you can then attach URLs with
30607 * // type-annotated parameters:
30608 * $stateProvider.state('users', {
30611 * }).state('users.item', {
30612 * url: "/{user:dbObject}",
30613 * controller: function($scope, $stateParams) {
30614 * // $stateParams.user will now be an object returned from
30615 * // the Users service
30621 this.type = function (name, definition, definitionFn) {
30622 if (!isDefined(definition)) return $types[name];
30623 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
30625 $types[name] = new Type(extend({ name: name }, definition));
30626 if (definitionFn) {
30627 typeQueue.push({ name: name, def: definitionFn });
30628 if (!enqueue) flushTypeQueue();
30633 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
30634 function flushTypeQueue() {
30635 while(typeQueue.length) {
30636 var type = typeQueue.shift();
30637 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
30638 angular.extend($types[type.name], injector.invoke(type.def));
30642 // Register default types. Store them in the prototype of $types.
30643 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
30644 $types = inherit($types, {});
30646 /* No need to document $get, since it returns this */
30647 this.$get = ['$injector', function ($injector) {
30648 injector = $injector;
30652 forEach(defaultTypes, function(type, name) {
30653 if (!$types[name]) $types[name] = new Type(type);
30658 this.Param = function Param(id, type, config, location) {
30660 config = unwrapShorthand(config);
30661 type = getType(config, type, location);
30662 var arrayMode = getArrayMode();
30663 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
30664 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
30665 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
30666 var isOptional = config.value !== undefined;
30667 var squash = getSquashPolicy(config, isOptional);
30668 var replace = getReplace(config, arrayMode, isOptional, squash);
30670 function unwrapShorthand(config) {
30671 var keys = isObject(config) ? objectKeys(config) : [];
30672 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
30673 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
30674 if (isShorthand) config = { value: config };
30675 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
30679 function getType(config, urlType, location) {
30680 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
30681 if (urlType) return urlType;
30682 if (!config.type) return (location === "config" ? $types.any : $types.string);
30683 return config.type instanceof Type ? config.type : new Type(config.type);
30686 // array config: param name (param[]) overrides default settings. explicit config overrides param name.
30687 function getArrayMode() {
30688 var arrayDefaults = { array: (location === "search" ? "auto" : false) };
30689 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
30690 return extend(arrayDefaults, arrayParamNomenclature, config).array;
30694 * returns false, true, or the squash value to indicate the "default parameter url squash policy".
30696 function getSquashPolicy(config, isOptional) {
30697 var squash = config.squash;
30698 if (!isOptional || squash === false) return false;
30699 if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
30700 if (squash === true || isString(squash)) return squash;
30701 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
30704 function getReplace(config, arrayMode, isOptional, squash) {
30705 var replace, configuredKeys, defaultPolicy = [
30706 { from: "", to: (isOptional || arrayMode ? undefined : "") },
30707 { from: null, to: (isOptional || arrayMode ? undefined : "") }
30709 replace = isArray(config.replace) ? config.replace : [];
30710 if (isString(squash))
30711 replace.push({ from: squash, to: undefined });
30712 configuredKeys = map(replace, function(item) { return item.from; } );
30713 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
30717 * [Internal] Get the default value of a parameter, which may be an injectable function.
30719 function $$getDefaultValue() {
30720 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30721 var defaultValue = injector.invoke(config.$$fn);
30722 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
30723 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
30724 return defaultValue;
30728 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
30729 * default value, which may be the result of an injectable function.
30731 function $value(value) {
30732 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
30733 function $replace(value) {
30734 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
30735 return replacement.length ? replacement[0] : value;
30737 value = $replace(value);
30738 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
30741 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
30746 location: location,
30750 isOptional: isOptional,
30752 dynamic: undefined,
30758 function ParamSet(params) {
30759 extend(this, params || {});
30762 ParamSet.prototype = {
30763 $$new: function() {
30764 return inherit(this, extend(new ParamSet(), { $$parent: this}));
30766 $$keys: function () {
30767 var keys = [], chain = [], parent = this,
30768 ignore = objectKeys(ParamSet.prototype);
30769 while (parent) { chain.push(parent); parent = parent.$$parent; }
30771 forEach(chain, function(paramset) {
30772 forEach(objectKeys(paramset), function(key) {
30773 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
30778 $$values: function(paramValues) {
30779 var values = {}, self = this;
30780 forEach(self.$$keys(), function(key) {
30781 values[key] = self[key].value(paramValues && paramValues[key]);
30785 $$equals: function(paramValues1, paramValues2) {
30786 var equal = true, self = this;
30787 forEach(self.$$keys(), function(key) {
30788 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
30789 if (!self[key].type.equals(left, right)) equal = false;
30793 $$validates: function $$validate(paramValues) {
30794 var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
30795 for (i = 0; i < keys.length; i++) {
30796 param = this[keys[i]];
30797 rawVal = paramValues[keys[i]];
30798 if ((rawVal === undefined || rawVal === null) && param.isOptional)
30799 break; // There was no parameter value, but the param is optional
30800 normalized = param.type.$normalize(rawVal);
30801 if (!param.type.is(normalized))
30802 return false; // The value was not of the correct Type, and could not be decoded to the correct Type
30803 encoded = param.type.encode(normalized);
30804 if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
30805 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
30809 $$parent: undefined
30812 this.ParamSet = ParamSet;
30815 // Register as a provider so it's available to other providers
30816 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
30817 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
30821 * @name ui.router.router.$urlRouterProvider
30823 * @requires ui.router.util.$urlMatcherFactoryProvider
30824 * @requires $locationProvider
30827 * `$urlRouterProvider` has the responsibility of watching `$location`.
30828 * When `$location` changes it runs through a list of rules one by one until a
30829 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
30830 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
30832 * There are several methods on `$urlRouterProvider` that make it useful to use directly
30833 * in your module config.
30835 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
30836 function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
30837 var rules = [], otherwise = null, interceptDeferred = false, listener;
30839 // Returns a string that is a prefix of all strings matching the RegExp
30840 function regExpPrefix(re) {
30841 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
30842 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
30845 // Interpolates matched values into a String.replace()-style pattern
30846 function interpolate(pattern, match) {
30847 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30848 return match[what === '$' ? 0 : Number(what)];
30854 * @name ui.router.router.$urlRouterProvider#rule
30855 * @methodOf ui.router.router.$urlRouterProvider
30858 * Defines rules that are used by `$urlRouterProvider` to find matches for
30863 * var app = angular.module('app', ['ui.router.router']);
30865 * app.config(function ($urlRouterProvider) {
30866 * // Here's an example of how you might allow case insensitive urls
30867 * $urlRouterProvider.rule(function ($injector, $location) {
30868 * var path = $location.path(),
30869 * normalized = path.toLowerCase();
30871 * if (path !== normalized) {
30872 * return normalized;
30878 * @param {object} rule Handler function that takes `$injector` and `$location`
30879 * services as arguments. You can use them to return a valid path as a string.
30881 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30883 this.rule = function (rule) {
30884 if (!isFunction(rule)) throw new Error("'rule' must be a function");
30891 * @name ui.router.router.$urlRouterProvider#otherwise
30892 * @methodOf ui.router.router.$urlRouterProvider
30895 * Defines a path that is used when an invalid route is requested.
30899 * var app = angular.module('app', ['ui.router.router']);
30901 * app.config(function ($urlRouterProvider) {
30902 * // if the path doesn't match any of the urls you configured
30903 * // otherwise will take care of routing the user to the
30905 * $urlRouterProvider.otherwise('/index');
30907 * // Example of using function rule as param
30908 * $urlRouterProvider.otherwise(function ($injector, $location) {
30909 * return '/a/valid/url';
30914 * @param {string|object} rule The url path you want to redirect to or a function
30915 * rule that returns the url path. The function version is passed two params:
30916 * `$injector` and `$location` services, and must return a url string.
30918 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30920 this.otherwise = function (rule) {
30921 if (isString(rule)) {
30922 var redirect = rule;
30923 rule = function () { return redirect; };
30925 else if (!isFunction(rule)) throw new Error("'rule' must be a function");
30931 function handleIfMatch($injector, handler, match) {
30932 if (!match) return false;
30933 var result = $injector.invoke(handler, handler, { $match: match });
30934 return isDefined(result) ? result : true;
30939 * @name ui.router.router.$urlRouterProvider#when
30940 * @methodOf ui.router.router.$urlRouterProvider
30943 * Registers a handler for a given url matching. if handle is a string, it is
30944 * treated as a redirect, and is interpolated according to the syntax of match
30945 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
30947 * If the handler is a function, it is injectable. It gets invoked if `$location`
30948 * matches. You have the option of inject the match object as `$match`.
30950 * The handler can return
30952 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
30953 * will continue trying to find another one that matches.
30954 * - **string** which is treated as a redirect and passed to `$location.url()`
30955 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
30959 * var app = angular.module('app', ['ui.router.router']);
30961 * app.config(function ($urlRouterProvider) {
30962 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
30963 * if ($state.$current.navigable !== state ||
30964 * !equalForKeys($match, $stateParams) {
30965 * $state.transitionTo(state, $match, false);
30971 * @param {string|object} what The incoming path that you want to redirect.
30972 * @param {string|object} handler The path you want to redirect your user to.
30974 this.when = function (what, handler) {
30975 var redirect, handlerIsString = isString(handler);
30976 if (isString(what)) what = $urlMatcherFactory.compile(what);
30978 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
30979 throw new Error("invalid 'handler' in when()");
30982 matcher: function (what, handler) {
30983 if (handlerIsString) {
30984 redirect = $urlMatcherFactory.compile(handler);
30985 handler = ['$match', function ($match) { return redirect.format($match); }];
30987 return extend(function ($injector, $location) {
30988 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
30990 prefix: isString(what.prefix) ? what.prefix : ''
30993 regex: function (what, handler) {
30994 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
30996 if (handlerIsString) {
30997 redirect = handler;
30998 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
31000 return extend(function ($injector, $location) {
31001 return handleIfMatch($injector, handler, what.exec($location.path()));
31003 prefix: regExpPrefix(what)
31008 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
31010 for (var n in check) {
31011 if (check[n]) return this.rule(strategies[n](what, handler));
31014 throw new Error("invalid 'what' in when()");
31019 * @name ui.router.router.$urlRouterProvider#deferIntercept
31020 * @methodOf ui.router.router.$urlRouterProvider
31023 * Disables (or enables) deferring location change interception.
31025 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
31026 * defer a transition but maintain the current URL), call this method at configuration time.
31027 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
31028 * `$locationChangeSuccess` event handler.
31032 * var app = angular.module('app', ['ui.router.router']);
31034 * app.config(function ($urlRouterProvider) {
31036 * // Prevent $urlRouter from automatically intercepting URL changes;
31037 * // this allows you to configure custom behavior in between
31038 * // location changes and route synchronization:
31039 * $urlRouterProvider.deferIntercept();
31041 * }).run(function ($rootScope, $urlRouter, UserService) {
31043 * $rootScope.$on('$locationChangeSuccess', function(e) {
31044 * // UserService is an example service for managing user state
31045 * if (UserService.isLoggedIn()) return;
31047 * // Prevent $urlRouter's default handler from firing
31048 * e.preventDefault();
31050 * UserService.handleLogin().then(function() {
31051 * // Once the user has logged in, sync the current URL
31052 * // to the router:
31053 * $urlRouter.sync();
31057 * // Configures $urlRouter's listener *after* your custom listener
31058 * $urlRouter.listen();
31062 * @param {boolean} defer Indicates whether to defer location change interception. Passing
31063 no parameter is equivalent to `true`.
31065 this.deferIntercept = function (defer) {
31066 if (defer === undefined) defer = true;
31067 interceptDeferred = defer;
31072 * @name ui.router.router.$urlRouter
31074 * @requires $location
31075 * @requires $rootScope
31076 * @requires $injector
31077 * @requires $browser
31083 $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
31084 function $get( $location, $rootScope, $injector, $browser) {
31086 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
31088 function appendBasePath(url, isHtml5, absolute) {
31089 if (baseHref === '/') return url;
31090 if (isHtml5) return baseHref.slice(0, -1) + url;
31091 if (absolute) return baseHref.slice(1) + url;
31095 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
31096 function update(evt) {
31097 if (evt && evt.defaultPrevented) return;
31098 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
31099 lastPushedUrl = undefined;
31100 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
31101 //if (ignoreUpdate) return true;
31103 function check(rule) {
31104 var handled = rule($injector, $location);
31106 if (!handled) return false;
31107 if (isString(handled)) $location.replace().url(handled);
31110 var n = rules.length, i;
31112 for (i = 0; i < n; i++) {
31113 if (check(rules[i])) return;
31115 // always check otherwise last to allow dynamic updates to the set of rules
31116 if (otherwise) check(otherwise);
31119 function listen() {
31120 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
31124 if (!interceptDeferred) listen();
31129 * @name ui.router.router.$urlRouter#sync
31130 * @methodOf ui.router.router.$urlRouter
31133 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
31134 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
31135 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
31136 * with the transition by calling `$urlRouter.sync()`.
31140 * angular.module('app', ['ui.router'])
31141 * .run(function($rootScope, $urlRouter) {
31142 * $rootScope.$on('$locationChangeSuccess', function(evt) {
31143 * // Halt state change from even starting
31144 * evt.preventDefault();
31145 * // Perform custom logic
31146 * var meetsRequirement = ...
31147 * // Continue with the update and state transition if logic allows
31148 * if (meetsRequirement) $urlRouter.sync();
31157 listen: function() {
31161 update: function(read) {
31163 location = $location.url();
31166 if ($location.url() === location) return;
31168 $location.url(location);
31169 $location.replace();
31172 push: function(urlMatcher, params, options) {
31173 var url = urlMatcher.format(params || {});
31175 // Handle the special hash param, if needed
31176 if (url !== null && params && params['#']) {
31177 url += '#' + params['#'];
31180 $location.url(url);
31181 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
31182 if (options && options.replace) $location.replace();
31187 * @name ui.router.router.$urlRouter#href
31188 * @methodOf ui.router.router.$urlRouter
31191 * A URL generation method that returns the compiled URL for a given
31192 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
31196 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
31199 * // $bob == "/about/bob";
31202 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
31203 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
31204 * @param {object=} options Options object. The options are:
31206 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
31208 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
31210 href: function(urlMatcher, params, options) {
31211 if (!urlMatcher.validates(params)) return null;
31213 var isHtml5 = $locationProvider.html5Mode();
31214 if (angular.isObject(isHtml5)) {
31215 isHtml5 = isHtml5.enabled;
31218 var url = urlMatcher.format(params);
31219 options = options || {};
31221 if (!isHtml5 && url !== null) {
31222 url = "#" + $locationProvider.hashPrefix() + url;
31225 // Handle special hash param, if needed
31226 if (url !== null && params && params['#']) {
31227 url += '#' + params['#'];
31230 url = appendBasePath(url, isHtml5, options.absolute);
31232 if (!options.absolute || !url) {
31236 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
31237 port = (port === 80 || port === 443 ? '' : ':' + port);
31239 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
31245 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
31249 * @name ui.router.state.$stateProvider
31251 * @requires ui.router.router.$urlRouterProvider
31252 * @requires ui.router.util.$urlMatcherFactoryProvider
31255 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
31258 * A state corresponds to a "place" in the application in terms of the overall UI and
31259 * navigation. A state describes (via the controller / template / view properties) what
31260 * the UI looks like and does at that place.
31262 * States often have things in common, and the primary way of factoring out these
31263 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
31266 * The `$stateProvider` provides interfaces to declare these states for your app.
31268 $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
31269 function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31271 var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
31273 // Builds state properties from definition passed to registerState()
31274 var stateBuilder = {
31276 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31277 // state.children = [];
31278 // if (parent) parent.children.push(state);
31279 parent: function(state) {
31280 if (isDefined(state.parent) && state.parent) return findState(state.parent);
31281 // regex matches any valid composite state name
31282 // would match "contact.list" but not "contacts"
31283 var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
31284 return compositeName ? findState(compositeName[1]) : root;
31287 // inherit 'data' from parent and override by own values (if any)
31288 data: function(state) {
31289 if (state.parent && state.parent.data) {
31290 state.data = state.self.data = extend({}, state.parent.data, state.data);
31295 // Build a URLMatcher if necessary, either via a relative or absolute URL
31296 url: function(state) {
31297 var url = state.url, config = { params: state.params || {} };
31299 if (isString(url)) {
31300 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
31301 return (state.parent.navigable || root).url.concat(url, config);
31304 if (!url || $urlMatcherFactory.isMatcher(url)) return url;
31305 throw new Error("Invalid url '" + url + "' in state '" + state + "'");
31308 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
31309 navigable: function(state) {
31310 return state.url ? state : (state.parent ? state.parent.navigable : null);
31313 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
31314 ownParams: function(state) {
31315 var params = state.url && state.url.params || new $$UMFP.ParamSet();
31316 forEach(state.params || {}, function(config, id) {
31317 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
31322 // Derive parameters for this state and ensure they're a super-set of parent's parameters
31323 params: function(state) {
31324 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
31327 // If there is no explicit multi-view configuration, make one up so we don't have
31328 // to handle both cases in the view directive later. Note that having an explicit
31329 // 'views' property will mean the default unnamed view properties are ignored. This
31330 // is also a good time to resolve view names to absolute names, so everything is a
31331 // straight lookup at link time.
31332 views: function(state) {
31335 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
31336 if (name.indexOf('@') < 0) name += '@' + state.parent.name;
31337 views[name] = view;
31342 // Keep a full path from the root down to this state as this is needed for state activation.
31343 path: function(state) {
31344 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
31347 // Speed up $state.contains() as it's used a lot
31348 includes: function(state) {
31349 var includes = state.parent ? extend({}, state.parent.includes) : {};
31350 includes[state.name] = true;
31357 function isRelative(stateName) {
31358 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
31361 function findState(stateOrName, base) {
31362 if (!stateOrName) return undefined;
31364 var isStr = isString(stateOrName),
31365 name = isStr ? stateOrName : stateOrName.name,
31366 path = isRelative(name);
31369 if (!base) throw new Error("No reference point given for path '" + name + "'");
31370 base = findState(base);
31372 var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
31374 for (; i < pathLength; i++) {
31375 if (rel[i] === "" && i === 0) {
31379 if (rel[i] === "^") {
31380 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
31381 current = current.parent;
31386 rel = rel.slice(i).join(".");
31387 name = current.name + (current.name && rel ? "." : "") + rel;
31389 var state = states[name];
31391 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
31397 function queueState(parentName, state) {
31398 if (!queue[parentName]) {
31399 queue[parentName] = [];
31401 queue[parentName].push(state);
31404 function flushQueuedChildren(parentName) {
31405 var queued = queue[parentName] || [];
31406 while(queued.length) {
31407 registerState(queued.shift());
31411 function registerState(state) {
31412 // Wrap a new object around the state so we can store our private details easily.
31413 state = inherit(state, {
31415 resolve: state.resolve || {},
31416 toString: function() { return this.name; }
31419 var name = state.name;
31420 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
31421 if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
31424 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
31425 : (isString(state.parent)) ? state.parent
31426 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
31429 // If parent is not registered yet, add state to queue and register later
31430 if (parentName && !states[parentName]) {
31431 return queueState(parentName, state.self);
31434 for (var key in stateBuilder) {
31435 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
31437 states[name] = state;
31439 // Register the state in the global state list and with $urlRouter if necessary.
31440 if (!state[abstractKey] && state.url) {
31441 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
31442 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
31443 $state.transitionTo(state, $match, { inherit: true, location: false });
31448 // Register any queued children
31449 flushQueuedChildren(name);
31454 // Checks text to see if it looks like a glob.
31455 function isGlob (text) {
31456 return text.indexOf('*') > -1;
31459 // Returns true if glob matches current $state name.
31460 function doesStateMatchGlob (glob) {
31461 var globSegments = glob.split('.'),
31462 segments = $state.$current.name.split('.');
31464 //match single stars
31465 for (var i = 0, l = globSegments.length; i < l; i++) {
31466 if (globSegments[i] === '*') {
31471 //match greedy starts
31472 if (globSegments[0] === '**') {
31473 segments = segments.slice(indexOf(segments, globSegments[1]));
31474 segments.unshift('**');
31476 //match greedy ends
31477 if (globSegments[globSegments.length - 1] === '**') {
31478 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
31479 segments.push('**');
31482 if (globSegments.length != segments.length) {
31486 return segments.join('') === globSegments.join('');
31490 // Implicit root state that is always active
31491 root = registerState({
31497 root.navigable = null;
31502 * @name ui.router.state.$stateProvider#decorator
31503 * @methodOf ui.router.state.$stateProvider
31506 * Allows you to extend (carefully) or override (at your own peril) the
31507 * `stateBuilder` object used internally by `$stateProvider`. This can be used
31508 * to add custom functionality to ui-router, for example inferring templateUrl
31509 * based on the state name.
31511 * When passing only a name, it returns the current (original or decorated) builder
31512 * function that matches `name`.
31514 * The builder functions that can be decorated are listed below. Though not all
31515 * necessarily have a good use case for decoration, that is up to you to decide.
31517 * In addition, users can attach custom decorators, which will generate new
31518 * properties within the state's internal definition. There is currently no clear
31519 * use-case for this beyond accessing internal states (i.e. $state.$current),
31520 * however, expect this to become increasingly relevant as we introduce additional
31521 * meta-programming features.
31523 * **Warning**: Decorators should not be interdependent because the order of
31524 * execution of the builder functions in non-deterministic. Builder functions
31525 * should only be dependent on the state definition object and super function.
31528 * Existing builder functions and current return values:
31530 * - **parent** `{object}` - returns the parent state object.
31531 * - **data** `{object}` - returns state data, including any inherited data that is not
31532 * overridden by own values (if any).
31533 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
31535 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
31537 * - **params** `{object}` - returns an array of state params that are ensured to
31538 * be a super-set of parent's params.
31539 * - **views** `{object}` - returns a views object where each key is an absolute view
31540 * name (i.e. "viewName@stateName") and each value is the config object
31541 * (template, controller) for the view. Even when you don't use the views object
31542 * explicitly on a state config, one is still created for you internally.
31543 * So by decorating this builder function you have access to decorating template
31544 * and controller properties.
31545 * - **ownParams** `{object}` - returns an array of params that belong to the state,
31546 * not including any params defined by ancestor states.
31547 * - **path** `{string}` - returns the full path from the root down to this state.
31548 * Needed for state activation.
31549 * - **includes** `{object}` - returns an object that includes every state that
31550 * would pass a `$state.includes()` test.
31554 * // Override the internal 'views' builder with a function that takes the state
31555 * // definition, and a reference to the internal function being overridden:
31556 * $stateProvider.decorator('views', function (state, parent) {
31558 * views = parent(state);
31560 * angular.forEach(views, function (config, name) {
31561 * var autoName = (state.name + '.' + name).replace('.', '/');
31562 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
31563 * result[name] = config;
31568 * $stateProvider.state('home', {
31570 * 'contact.list': { controller: 'ListController' },
31571 * 'contact.item': { controller: 'ItemController' }
31577 * $state.go('home');
31578 * // Auto-populates list and item views with /partials/home/contact/list.html,
31579 * // and /partials/home/contact/item.html, respectively.
31582 * @param {string} name The name of the builder function to decorate.
31583 * @param {object} func A function that is responsible for decorating the original
31584 * builder function. The function receives two parameters:
31586 * - `{object}` - state - The state config object.
31587 * - `{object}` - super - The original builder function.
31589 * @return {object} $stateProvider - $stateProvider instance
31591 this.decorator = decorator;
31592 function decorator(name, func) {
31593 /*jshint validthis: true */
31594 if (isString(name) && !isDefined(func)) {
31595 return stateBuilder[name];
31597 if (!isFunction(func) || !isString(name)) {
31600 if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
31601 stateBuilder.$delegates[name] = stateBuilder[name];
31603 stateBuilder[name] = func;
31609 * @name ui.router.state.$stateProvider#state
31610 * @methodOf ui.router.state.$stateProvider
31613 * Registers a state configuration under a given state name. The stateConfig object
31614 * has the following acceptable properties.
31616 * @param {string} name A unique state name, e.g. "home", "about", "contacts".
31617 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
31618 * @param {object} stateConfig State configuration object.
31619 * @param {string|function=} stateConfig.template
31620 * <a id='template'></a>
31621 * html template as a string or a function that returns
31622 * an html template as a string which should be used by the uiView directives. This property
31623 * takes precedence over templateUrl.
31625 * If `template` is a function, it will be called with the following parameters:
31627 * - {array.<object>} - state parameters extracted from the current $location.path() by
31628 * applying the current state
31631 * "<h1>inline template definition</h1>" +
31632 * "<div ui-view></div>"</pre>
31633 * <pre>template: function(params) {
31634 * return "<h1>generated template</h1>"; }</pre>
31637 * @param {string|function=} stateConfig.templateUrl
31638 * <a id='templateUrl'></a>
31640 * path or function that returns a path to an html
31641 * template that should be used by uiView.
31643 * If `templateUrl` is a function, it will be called with the following parameters:
31645 * - {array.<object>} - state parameters extracted from the current $location.path() by
31646 * applying the current state
31648 * <pre>templateUrl: "home.html"</pre>
31649 * <pre>templateUrl: function(params) {
31650 * return myTemplates[params.pageId]; }</pre>
31652 * @param {function=} stateConfig.templateProvider
31653 * <a id='templateProvider'></a>
31654 * Provider function that returns HTML content string.
31655 * <pre> templateProvider:
31656 * function(MyTemplateService, params) {
31657 * return MyTemplateService.getTemplate(params.pageId);
31660 * @param {string|function=} stateConfig.controller
31661 * <a id='controller'></a>
31663 * Controller fn that should be associated with newly
31664 * related scope or the name of a registered controller if passed as a string.
31665 * Optionally, the ControllerAs may be declared here.
31666 * <pre>controller: "MyRegisteredController"</pre>
31668 * "MyRegisteredController as fooCtrl"}</pre>
31669 * <pre>controller: function($scope, MyService) {
31670 * $scope.data = MyService.getData(); }</pre>
31672 * @param {function=} stateConfig.controllerProvider
31673 * <a id='controllerProvider'></a>
31675 * Injectable provider function that returns the actual controller or string.
31676 * <pre>controllerProvider:
31677 * function(MyResolveData) {
31678 * if (MyResolveData.foo)
31680 * else if (MyResolveData.bar)
31681 * return "BarCtrl";
31682 * else return function($scope) {
31683 * $scope.baz = "Qux";
31687 * @param {string=} stateConfig.controllerAs
31688 * <a id='controllerAs'></a>
31690 * A controller alias name. If present the controller will be
31691 * published to scope under the controllerAs name.
31692 * <pre>controllerAs: "myCtrl"</pre>
31694 * @param {string|object=} stateConfig.parent
31695 * <a id='parent'></a>
31696 * Optionally specifies the parent state of this state.
31698 * <pre>parent: 'parentState'</pre>
31699 * <pre>parent: parentState // JS variable</pre>
31701 * @param {object=} stateConfig.resolve
31702 * <a id='resolve'></a>
31704 * An optional map<string, function> of dependencies which
31705 * should be injected into the controller. If any of these dependencies are promises,
31706 * the router will wait for them all to be resolved before the controller is instantiated.
31707 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
31708 * and the values of the resolved promises are injected into any controllers that reference them.
31709 * If any of the promises are rejected the $stateChangeError event is fired.
31711 * The map object is:
31713 * - key - {string}: name of dependency to be injected into controller
31714 * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
31715 * it is injected and return value it treated as dependency. If result is a promise, it is
31716 * resolved before its value is injected into controller.
31720 * function($http, $stateParams) {
31721 * return $http.get("/api/foos/"+stateParams.fooID);
31725 * @param {string=} stateConfig.url
31728 * A url fragment with optional parameters. When a state is navigated or
31729 * transitioned to, the `$stateParams` service will be populated with any
31730 * parameters that were passed.
31732 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
31733 * more details on acceptable patterns )
31736 * <pre>url: "/home"
31737 * url: "/users/:userid"
31738 * url: "/books/{bookid:[a-zA-Z_-]}"
31739 * url: "/books/{categoryid:int}"
31740 * url: "/books/{publishername:string}/{categoryid:int}"
31741 * url: "/messages?before&after"
31742 * url: "/messages?{before:date}&{after:date}"
31743 * url: "/messages/:mailboxid?{before:date}&{after:date}"
31746 * @param {object=} stateConfig.views
31747 * <a id='views'></a>
31748 * an optional map<string, object> which defined multiple views, or targets views
31749 * manually/explicitly.
31753 * Targets three named `ui-view`s in the parent state's template
31756 * controller: "headerCtrl",
31757 * templateUrl: "header.html"
31759 * controller: "bodyCtrl",
31760 * templateUrl: "body.html"
31762 * controller: "footCtrl",
31763 * templateUrl: "footer.html"
31767 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
31770 * controller: "msgHeaderCtrl",
31771 * templateUrl: "msgHeader.html"
31773 * controller: "messagesCtrl",
31774 * templateUrl: "messages.html"
31778 * @param {boolean=} [stateConfig.abstract=false]
31779 * <a id='abstract'></a>
31780 * An abstract state will never be directly activated,
31781 * but can provide inherited properties to its common children states.
31782 * <pre>abstract: true</pre>
31784 * @param {function=} stateConfig.onEnter
31785 * <a id='onEnter'></a>
31787 * Callback function for when a state is entered. Good way
31788 * to trigger an action or dispatch an event, such as opening a dialog.
31789 * If minifying your scripts, make sure to explictly annotate this function,
31790 * because it won't be automatically annotated by your build tools.
31792 * <pre>onEnter: function(MyService, $stateParams) {
31793 * MyService.foo($stateParams.myParam);
31796 * @param {function=} stateConfig.onExit
31797 * <a id='onExit'></a>
31799 * Callback function for when a state is exited. Good way to
31800 * trigger an action or dispatch an event, such as opening a dialog.
31801 * If minifying your scripts, make sure to explictly annotate this function,
31802 * because it won't be automatically annotated by your build tools.
31804 * <pre>onExit: function(MyService, $stateParams) {
31805 * MyService.cleanup($stateParams.myParam);
31808 * @param {boolean=} [stateConfig.reloadOnSearch=true]
31809 * <a id='reloadOnSearch'></a>
31811 * If `false`, will not retrigger the same state
31812 * just because a search/query parameter has changed (via $location.search() or $location.hash()).
31813 * Useful for when you'd like to modify $location.search() without triggering a reload.
31814 * <pre>reloadOnSearch: false</pre>
31816 * @param {object=} stateConfig.data
31817 * <a id='data'></a>
31819 * Arbitrary data object, useful for custom configuration. The parent state's `data` is
31820 * prototypally inherited. In other words, adding a data property to a state adds it to
31821 * the entire subtree via prototypal inheritance.
31824 * requiredRole: 'foo'
31827 * @param {object=} stateConfig.params
31828 * <a id='params'></a>
31830 * A map which optionally configures parameters declared in the `url`, or
31831 * defines additional non-url parameters. For each parameter being
31832 * configured, add a configuration object keyed to the name of the parameter.
31834 * Each parameter configuration object may contain the following properties:
31836 * - ** value ** - {object|function=}: specifies the default value for this
31837 * parameter. This implicitly sets this parameter as optional.
31839 * When UI-Router routes to a state and no value is
31840 * specified for this parameter in the URL or transition, the
31841 * default value will be used instead. If `value` is a function,
31842 * it will be injected and invoked, and the return value used.
31844 * *Note*: `undefined` is treated as "no default value" while `null`
31845 * is treated as "the default value is `null`".
31847 * *Shorthand*: If you only need to configure the default value of the
31848 * parameter, you may use a shorthand syntax. In the **`params`**
31849 * map, instead mapping the param name to a full parameter configuration
31850 * object, simply set map it to the default parameter value, e.g.:
31852 * <pre>// define a parameter's default value
31854 * param1: { value: "defaultValue" }
31856 * // shorthand default values
31858 * param1: "defaultValue",
31859 * param2: "param2Default"
31862 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
31863 * treated as an array of values. If you specified a Type, the value will be
31864 * treated as an array of the specified Type. Note: query parameter values
31865 * default to a special `"auto"` mode.
31867 * For query parameters in `"auto"` mode, if multiple values for a single parameter
31868 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
31869 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
31870 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
31871 * value (e.g.: `{ foo: '1' }`).
31874 * param1: { array: true }
31877 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
31878 * the current parameter value is the same as the default value. If `squash` is not set, it uses the
31879 * configured default squash policy.
31880 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
31882 * There are three squash settings:
31884 * - false: The parameter's default value is not squashed. It is encoded and included in the URL
31885 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
31886 * by slashes in the state's `url` declaration, then one of those slashes are omitted.
31887 * This can allow for cleaner looking URLs.
31888 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
31892 * value: "defaultId",
31895 * // squash "defaultValue" to "~"
31898 * value: "defaultValue",
31906 * // Some state name examples
31908 * // stateName can be a single top-level name (must be unique).
31909 * $stateProvider.state("home", {});
31911 * // Or it can be a nested state name. This state is a child of the
31912 * // above "home" state.
31913 * $stateProvider.state("home.newest", {});
31915 * // Nest states as deeply as needed.
31916 * $stateProvider.state("home.newest.abc.xyz.inception", {});
31918 * // state() returns $stateProvider, so you can chain state declarations.
31920 * .state("home", {})
31921 * .state("about", {})
31922 * .state("contacts", {});
31926 this.state = state;
31927 function state(name, definition) {
31928 /*jshint validthis: true */
31929 if (isObject(name)) definition = name;
31930 else definition.name = name;
31931 registerState(definition);
31937 * @name ui.router.state.$state
31939 * @requires $rootScope
31941 * @requires ui.router.state.$view
31942 * @requires $injector
31943 * @requires ui.router.util.$resolve
31944 * @requires ui.router.state.$stateParams
31945 * @requires ui.router.router.$urlRouter
31947 * @property {object} params A param object, e.g. {sectionId: section.id)}, that
31948 * you'd like to test against the current active state.
31949 * @property {object} current A reference to the state's config object. However
31950 * you passed it in. Useful for accessing custom data.
31951 * @property {object} transition Currently pending transition. A promise that'll
31952 * resolve or reject.
31955 * `$state` service is responsible for representing states as well as transitioning
31956 * between them. It also provides interfaces to ask for current state or even states
31957 * you're coming from.
31960 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
31961 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
31963 var TransitionSuperseded = $q.reject(new Error('transition superseded'));
31964 var TransitionPrevented = $q.reject(new Error('transition prevented'));
31965 var TransitionAborted = $q.reject(new Error('transition aborted'));
31966 var TransitionFailed = $q.reject(new Error('transition failed'));
31968 // Handles the case where a state which is the target of a transition is not found, and the user
31969 // can optionally retry or defer the transition
31970 function handleRedirect(redirect, state, params, options) {
31973 * @name ui.router.state.$state#$stateNotFound
31974 * @eventOf ui.router.state.$state
31975 * @eventType broadcast on root scope
31977 * Fired when a requested state **cannot be found** using the provided state name during transition.
31978 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
31979 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
31980 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
31981 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
31983 * @param {Object} event Event object.
31984 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
31985 * @param {State} fromState Current state object.
31986 * @param {Object} fromParams Current state params.
31991 * // somewhere, assume lazy.state has not been defined
31992 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
31994 * // somewhere else
31995 * $scope.$on('$stateNotFound',
31996 * function(event, unfoundState, fromState, fromParams){
31997 * console.log(unfoundState.to); // "lazy.state"
31998 * console.log(unfoundState.toParams); // {a:1, b:2}
31999 * console.log(unfoundState.options); // {inherit:false} + default options
32003 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
32005 if (evt.defaultPrevented) {
32006 $urlRouter.update();
32007 return TransitionAborted;
32014 // Allow the handler to return a promise to defer state lookup retry
32015 if (options.$retry) {
32016 $urlRouter.update();
32017 return TransitionFailed;
32019 var retryTransition = $state.transition = $q.when(evt.retry);
32021 retryTransition.then(function() {
32022 if (retryTransition !== $state.transition) return TransitionSuperseded;
32023 redirect.options.$retry = true;
32024 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
32026 return TransitionAborted;
32028 $urlRouter.update();
32030 return retryTransition;
32033 root.locals = { resolve: null, globals: { $stateParams: {} } };
32037 current: root.self,
32044 * @name ui.router.state.$state#reload
32045 * @methodOf ui.router.state.$state
32048 * A method that force reloads the current state. All resolves are re-resolved,
32049 * controllers reinstantiated, and events re-fired.
32053 * var app angular.module('app', ['ui.router']);
32055 * app.controller('ctrl', function ($scope, $state) {
32056 * $scope.reload = function(){
32062 * `reload()` is just an alias for:
32064 * $state.transitionTo($state.current, $stateParams, {
32065 * reload: true, inherit: false, notify: true
32069 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
32072 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
32073 * //and current state is 'contacts.detail.item'
32074 * var app angular.module('app', ['ui.router']);
32076 * app.controller('ctrl', function ($scope, $state) {
32077 * $scope.reload = function(){
32078 * //will reload 'contact.detail' and 'contact.detail.item' states
32079 * $state.reload('contact.detail');
32084 * `reload()` is just an alias for:
32086 * $state.transitionTo($state.current, $stateParams, {
32087 * reload: true, inherit: false, notify: true
32091 * @returns {promise} A promise representing the state of the new transition. See
32092 * {@link ui.router.state.$state#methods_go $state.go}.
32094 $state.reload = function reload(state) {
32095 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
32100 * @name ui.router.state.$state#go
32101 * @methodOf ui.router.state.$state
32104 * Convenience method for transitioning to a new state. `$state.go` calls
32105 * `$state.transitionTo` internally but automatically sets options to
32106 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
32107 * This allows you to easily use an absolute or relative to path and specify
32108 * only the parameters you'd like to update (while letting unspecified parameters
32109 * inherit from the currently active ancestor states).
32113 * var app = angular.module('app', ['ui.router']);
32115 * app.controller('ctrl', function ($scope, $state) {
32116 * $scope.changeState = function () {
32117 * $state.go('contact.detail');
32121 * <img src='../ngdoc_assets/StateGoExamples.png'/>
32123 * @param {string} to Absolute state name or relative state path. Some examples:
32125 * - `$state.go('contact.detail')` - will go to the `contact.detail` state
32126 * - `$state.go('^')` - will go to a parent state
32127 * - `$state.go('^.sibling')` - will go to a sibling state
32128 * - `$state.go('.child.grandchild')` - will go to grandchild state
32130 * @param {object=} params A map of the parameters that will be sent to the state,
32131 * will populate $stateParams. Any parameters that are not specified will be inherited from currently
32132 * defined parameters. This allows, for example, going to a sibling state that shares parameters
32133 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
32134 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
32135 * will get you all current parameters, etc.
32136 * @param {object=} options Options object. The options are:
32138 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32139 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32140 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32141 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32142 * defines which state to be relative from.
32143 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32144 * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
32145 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32146 * use this when you want to force a reload when *everything* is the same, including search params.
32148 * @returns {promise} A promise representing the state of the new transition.
32150 * Possible success values:
32154 * <br/>Possible rejection values:
32156 * - 'transition superseded' - when a newer transition has been started after this one
32157 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
32158 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
32159 * when a `$stateNotFound` `event.retry` promise errors.
32160 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
32161 * - *resolve error* - when an error has occurred with a `resolve`
32164 $state.go = function go(to, params, options) {
32165 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
32170 * @name ui.router.state.$state#transitionTo
32171 * @methodOf ui.router.state.$state
32174 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
32175 * uses `transitionTo` internally. `$state.go` is recommended in most situations.
32179 * var app = angular.module('app', ['ui.router']);
32181 * app.controller('ctrl', function ($scope, $state) {
32182 * $scope.changeState = function () {
32183 * $state.transitionTo('contact.detail');
32188 * @param {string} to State name.
32189 * @param {object=} toParams A map of the parameters that will be sent to the state,
32190 * will populate $stateParams.
32191 * @param {object=} options Options object. The options are:
32193 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32194 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32195 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
32196 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
32197 * defines which state to be relative from.
32198 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32199 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
32200 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32201 * use this when you want to force a reload when *everything* is the same, including search params.
32202 * if String, then will reload the state with the name given in reload, and any children.
32203 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
32205 * @returns {promise} A promise representing the state of the new transition. See
32206 * {@link ui.router.state.$state#methods_go $state.go}.
32208 $state.transitionTo = function transitionTo(to, toParams, options) {
32209 toParams = toParams || {};
32211 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
32214 var from = $state.$current, fromParams = $state.params, fromPath = from.path;
32215 var evt, toState = findState(to, options.relative);
32217 // Store the hash param for later (since it will be stripped out by various methods)
32218 var hash = toParams['#'];
32220 if (!isDefined(toState)) {
32221 var redirect = { to: to, toParams: toParams, options: options };
32222 var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
32224 if (redirectResult) {
32225 return redirectResult;
32228 // Always retry once if the $stateNotFound was not prevented
32229 // (handles either redirect changed or state lazy-definition)
32231 toParams = redirect.toParams;
32232 options = redirect.options;
32233 toState = findState(to, options.relative);
32235 if (!isDefined(toState)) {
32236 if (!options.relative) throw new Error("No such state '" + to + "'");
32237 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
32240 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
32241 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
32242 if (!toState.params.$$validates(toParams)) return TransitionFailed;
32244 toParams = toState.params.$$values(toParams);
32247 var toPath = to.path;
32249 // Starting from the root of the path, keep all levels that haven't changed
32250 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
32252 if (!options.reload) {
32253 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
32254 locals = toLocals[keep] = state.locals;
32256 state = toPath[keep];
32258 } else if (isString(options.reload) || isObject(options.reload)) {
32259 if (isObject(options.reload) && !options.reload.name) {
32260 throw new Error('Invalid reload state object');
32263 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
32264 if (options.reload && !reloadState) {
32265 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
32268 while (state && state === fromPath[keep] && state !== reloadState) {
32269 locals = toLocals[keep] = state.locals;
32271 state = toPath[keep];
32275 // If we're going to the same state and all locals are kept, we've got nothing to do.
32276 // But clear 'transition', as we still want to cancel any other pending transitions.
32277 // TODO: We may not want to bump 'transition' if we're called from a location change
32278 // that we've initiated ourselves, because we might accidentally abort a legitimate
32279 // transition initiated from code?
32280 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
32281 if (hash) toParams['#'] = hash;
32282 $state.params = toParams;
32283 copy($state.params, $stateParams);
32284 if (options.location && to.navigable && to.navigable.url) {
32285 $urlRouter.push(to.navigable.url, toParams, {
32286 $$avoidResync: true, replace: options.location === 'replace'
32288 $urlRouter.update(true);
32290 $state.transition = null;
32291 return $q.when($state.current);
32294 // Filter parameters before we pass them to event handlers etc.
32295 toParams = filterByKeys(to.params.$$keys(), toParams || {});
32297 // Broadcast start event and cancel the transition if requested
32298 if (options.notify) {
32301 * @name ui.router.state.$state#$stateChangeStart
32302 * @eventOf ui.router.state.$state
32303 * @eventType broadcast on root scope
32305 * Fired when the state transition **begins**. You can use `event.preventDefault()`
32306 * to prevent the transition from happening and then the transition promise will be
32307 * rejected with a `'transition prevented'` value.
32309 * @param {Object} event Event object.
32310 * @param {State} toState The state being transitioned to.
32311 * @param {Object} toParams The params supplied to the `toState`.
32312 * @param {State} fromState The current state, pre-transition.
32313 * @param {Object} fromParams The params supplied to the `fromState`.
32318 * $rootScope.$on('$stateChangeStart',
32319 * function(event, toState, toParams, fromState, fromParams){
32320 * event.preventDefault();
32321 * // transitionTo() promise will be rejected with
32322 * // a 'transition prevented' error
32326 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
32327 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
32328 $urlRouter.update();
32329 return TransitionPrevented;
32333 // Resolve locals for the remaining states, but don't update any global state just
32334 // yet -- if anything fails to resolve the current state needs to remain untouched.
32335 // We also set up an inheritance chain for the locals here. This allows the view directive
32336 // to quickly look up the correct definition for each view in the current state. Even
32337 // though we create the locals object itself outside resolveState(), it is initially
32338 // empty and gets filled asynchronously. We need to keep track of the promise for the
32339 // (fully resolved) current locals, and pass this down the chain.
32340 var resolved = $q.when(locals);
32342 for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
32343 locals = toLocals[l] = inherit(locals);
32344 resolved = resolveState(state, toParams, state === to, resolved, locals, options);
32347 // Once everything is resolved, we are ready to perform the actual transition
32348 // and return a promise for the new state. We also keep track of what the
32349 // current promise is, so that we can detect overlapping transitions and
32350 // keep only the outcome of the last transition.
32351 var transition = $state.transition = resolved.then(function () {
32352 var l, entering, exiting;
32354 if ($state.transition !== transition) return TransitionSuperseded;
32356 // Exit 'from' states not kept
32357 for (l = fromPath.length - 1; l >= keep; l--) {
32358 exiting = fromPath[l];
32359 if (exiting.self.onExit) {
32360 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
32362 exiting.locals = null;
32365 // Enter 'to' states not kept
32366 for (l = keep; l < toPath.length; l++) {
32367 entering = toPath[l];
32368 entering.locals = toLocals[l];
32369 if (entering.self.onEnter) {
32370 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
32374 // Re-add the saved hash before we start returning things
32375 if (hash) toParams['#'] = hash;
32377 // Run it again, to catch any transitions in callbacks
32378 if ($state.transition !== transition) return TransitionSuperseded;
32380 // Update globals in $state
32381 $state.$current = to;
32382 $state.current = to.self;
32383 $state.params = toParams;
32384 copy($state.params, $stateParams);
32385 $state.transition = null;
32387 if (options.location && to.navigable) {
32388 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
32389 $$avoidResync: true, replace: options.location === 'replace'
32393 if (options.notify) {
32396 * @name ui.router.state.$state#$stateChangeSuccess
32397 * @eventOf ui.router.state.$state
32398 * @eventType broadcast on root scope
32400 * Fired once the state transition is **complete**.
32402 * @param {Object} event Event object.
32403 * @param {State} toState The state being transitioned to.
32404 * @param {Object} toParams The params supplied to the `toState`.
32405 * @param {State} fromState The current state, pre-transition.
32406 * @param {Object} fromParams The params supplied to the `fromState`.
32408 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
32410 $urlRouter.update(true);
32412 return $state.current;
32413 }, function (error) {
32414 if ($state.transition !== transition) return TransitionSuperseded;
32416 $state.transition = null;
32419 * @name ui.router.state.$state#$stateChangeError
32420 * @eventOf ui.router.state.$state
32421 * @eventType broadcast on root scope
32423 * Fired when an **error occurs** during transition. It's important to note that if you
32424 * have any errors in your resolve functions (javascript errors, non-existent services, etc)
32425 * they will not throw traditionally. You must listen for this $stateChangeError event to
32426 * catch **ALL** errors.
32428 * @param {Object} event Event object.
32429 * @param {State} toState The state being transitioned to.
32430 * @param {Object} toParams The params supplied to the `toState`.
32431 * @param {State} fromState The current state, pre-transition.
32432 * @param {Object} fromParams The params supplied to the `fromState`.
32433 * @param {Error} error The resolve error object.
32435 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
32437 if (!evt.defaultPrevented) {
32438 $urlRouter.update();
32441 return $q.reject(error);
32449 * @name ui.router.state.$state#is
32450 * @methodOf ui.router.state.$state
32453 * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
32454 * but only checks for the full state name. If params is supplied then it will be
32455 * tested for strict equality against the current active params object, so all params
32456 * must match with none missing and no extras.
32460 * $state.$current.name = 'contacts.details.item';
32463 * $state.is('contact.details.item'); // returns true
32464 * $state.is(contactDetailItemStateObject); // returns true
32466 * // relative name (. and ^), typically from a template
32467 * // E.g. from the 'contacts.details' template
32468 * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
32471 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
32472 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
32473 * to test against the current active state.
32474 * @param {object=} options An options object. The options are:
32476 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
32477 * test relative to `options.relative` state (or name).
32479 * @returns {boolean} Returns true if it is the state.
32481 $state.is = function is(stateOrName, params, options) {
32482 options = extend({ relative: $state.$current }, options || {});
32483 var state = findState(stateOrName, options.relative);
32485 if (!isDefined(state)) { return undefined; }
32486 if ($state.$current !== state) { return false; }
32487 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32492 * @name ui.router.state.$state#includes
32493 * @methodOf ui.router.state.$state
32496 * A method to determine if the current active state is equal to or is the child of the
32497 * state stateName. If any params are passed then they will be tested for a match as well.
32498 * Not all the parameters need to be passed, just the ones you'd like to test for equality.
32501 * Partial and relative names
32503 * $state.$current.name = 'contacts.details.item';
32505 * // Using partial names
32506 * $state.includes("contacts"); // returns true
32507 * $state.includes("contacts.details"); // returns true
32508 * $state.includes("contacts.details.item"); // returns true
32509 * $state.includes("contacts.list"); // returns false
32510 * $state.includes("about"); // returns false
32512 * // Using relative names (. and ^), typically from a template
32513 * // E.g. from the 'contacts.details' template
32514 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
32517 * Basic globbing patterns
32519 * $state.$current.name = 'contacts.details.item.url';
32521 * $state.includes("*.details.*.*"); // returns true
32522 * $state.includes("*.details.**"); // returns true
32523 * $state.includes("**.item.**"); // returns true
32524 * $state.includes("*.details.item.url"); // returns true
32525 * $state.includes("*.details.*.url"); // returns true
32526 * $state.includes("*.details.*"); // returns false
32527 * $state.includes("item.**"); // returns false
32530 * @param {string} stateOrName A partial name, relative name, or glob pattern
32531 * to be searched for within the current state name.
32532 * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
32533 * that you'd like to test against the current active state.
32534 * @param {object=} options An options object. The options are:
32536 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
32537 * .includes will test relative to `options.relative` state (or name).
32539 * @returns {boolean} Returns true if it does include the state
32541 $state.includes = function includes(stateOrName, params, options) {
32542 options = extend({ relative: $state.$current }, options || {});
32543 if (isString(stateOrName) && isGlob(stateOrName)) {
32544 if (!doesStateMatchGlob(stateOrName)) {
32547 stateOrName = $state.$current.name;
32550 var state = findState(stateOrName, options.relative);
32551 if (!isDefined(state)) { return undefined; }
32552 if (!isDefined($state.$current.includes[state.name])) { return false; }
32553 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
32559 * @name ui.router.state.$state#href
32560 * @methodOf ui.router.state.$state
32563 * A url generation method that returns the compiled url for the given state populated with the given params.
32567 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
32570 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
32571 * @param {object=} params An object of parameter values to fill the state's required parameters.
32572 * @param {object=} options Options object. The options are:
32574 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
32575 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
32576 * ancestor with a valid url).
32577 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32578 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32579 * defines which state to be relative from.
32580 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
32582 * @returns {string} compiled state url
32584 $state.href = function href(stateOrName, params, options) {
32589 relative: $state.$current
32592 var state = findState(stateOrName, options.relative);
32594 if (!isDefined(state)) return null;
32595 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
32597 var nav = (state && options.lossy) ? state.navigable : state;
32599 if (!nav || nav.url === undefined || nav.url === null) {
32602 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
32603 absolute: options.absolute
32609 * @name ui.router.state.$state#get
32610 * @methodOf ui.router.state.$state
32613 * Returns the state configuration object for any specific state or all states.
32615 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
32616 * the requested state. If not provided, returns an array of ALL state configs.
32617 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
32618 * @returns {Object|Array} State configuration object or array of all objects.
32620 $state.get = function (stateOrName, context) {
32621 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
32622 var state = findState(stateOrName, context || $state.$current);
32623 return (state && state.self) ? state.self : null;
32626 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
32627 // Make a restricted $stateParams with only the parameters that apply to this state if
32628 // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
32629 // we also need $stateParams to be available for any $injector calls we make during the
32630 // dependency resolution process.
32631 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
32632 var locals = { $stateParams: $stateParams };
32634 // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
32635 // We're also including $stateParams in this; that way the parameters are restricted
32636 // to the set that should be visible to the state, and are independent of when we update
32637 // the global $state and $stateParams values.
32638 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
32639 var promises = [dst.resolve.then(function (globals) {
32640 dst.globals = globals;
32642 if (inherited) promises.push(inherited);
32644 function resolveViews() {
32645 var viewsPromises = [];
32647 // Resolve template and dependencies for all views.
32648 forEach(state.views, function (view, name) {
32649 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
32650 injectables.$template = [ function () {
32651 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
32654 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
32655 // References to the controller (only instantiated at link time)
32656 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
32657 var injectLocals = angular.extend({}, injectables, dst.globals);
32658 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
32660 result.$$controller = view.controller;
32662 // Provide access to the state itself for internal use
32663 result.$$state = state;
32664 result.$$controllerAs = view.controllerAs;
32665 dst[name] = result;
32669 return $q.all(viewsPromises).then(function(){
32670 return dst.globals;
32674 // Wait for all the promises and then return the activation object
32675 return $q.all(promises).then(resolveViews).then(function (values) {
32683 function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
32684 // Return true if there are no differences in non-search (path/object) params, false if there are differences
32685 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
32686 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
32687 function notSearchParam(key) {
32688 return fromAndToState.params[key].location != "search";
32690 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
32691 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
32692 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
32693 return nonQueryParamSet.$$equals(fromParams, toParams);
32696 // If reload was not explicitly requested
32697 // and we're transitioning to the same state we're already in
32698 // and the locals didn't change
32699 // or they changed in a way that doesn't merit reloading
32700 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
32701 // Then return true.
32702 if (!options.reload && to === from &&
32703 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
32709 angular.module('ui.router.state')
32710 .value('$stateParams', {})
32711 .provider('$state', $StateProvider);
32714 $ViewProvider.$inject = [];
32715 function $ViewProvider() {
32720 * @name ui.router.state.$view
32722 * @requires ui.router.util.$templateFactory
32723 * @requires $rootScope
32728 $get.$inject = ['$rootScope', '$templateFactory'];
32729 function $get( $rootScope, $templateFactory) {
32731 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
32734 * @name ui.router.state.$view#load
32735 * @methodOf ui.router.state.$view
32739 * @param {string} name name
32740 * @param {object} options option object.
32742 load: function load(name, options) {
32743 var result, defaults = {
32744 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
32746 options = extend(defaults, options);
32748 if (options.view) {
32749 result = $templateFactory.fromConfig(options.view, options.params, options.locals);
32751 if (result && options.notify) {
32754 * @name ui.router.state.$state#$viewContentLoading
32755 * @eventOf ui.router.state.$view
32756 * @eventType broadcast on root scope
32759 * Fired once the view **begins loading**, *before* the DOM is rendered.
32761 * @param {Object} event Event object.
32762 * @param {Object} viewConfig The view config properties (template, controller, etc).
32767 * $scope.$on('$viewContentLoading',
32768 * function(event, viewConfig){
32769 * // Access to all the view config properties.
32770 * // and one special property 'targetView'
32771 * // viewConfig.targetView
32775 $rootScope.$broadcast('$viewContentLoading', options);
32783 angular.module('ui.router.state').provider('$view', $ViewProvider);
32787 * @name ui.router.state.$uiViewScrollProvider
32790 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
32792 function $ViewScrollProvider() {
32794 var useAnchorScroll = false;
32798 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
32799 * @methodOf ui.router.state.$uiViewScrollProvider
32802 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
32803 * scrolling based on the url anchor.
32805 this.useAnchorScroll = function () {
32806 useAnchorScroll = true;
32811 * @name ui.router.state.$uiViewScroll
32813 * @requires $anchorScroll
32814 * @requires $timeout
32817 * When called with a jqLite element, it scrolls the element into view (after a
32818 * `$timeout` so the DOM has time to refresh).
32820 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
32821 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
32823 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
32824 if (useAnchorScroll) {
32825 return $anchorScroll;
32828 return function ($element) {
32829 return $timeout(function () {
32830 $element[0].scrollIntoView();
32836 angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
32840 * @name ui.router.state.directive:ui-view
32842 * @requires ui.router.state.$state
32843 * @requires $compile
32844 * @requires $controller
32845 * @requires $injector
32846 * @requires ui.router.state.$uiViewScroll
32847 * @requires $document
32852 * The ui-view directive tells $state where to place your templates.
32854 * @param {string=} name A view name. The name should be unique amongst the other views in the
32855 * same state. You can have views of the same name that live in different states.
32857 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
32858 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
32859 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
32860 * scroll ui-view elements into view when they are populated during a state activation.
32862 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
32863 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
32865 * @param {string=} onload Expression to evaluate whenever the view updates.
32868 * A view can be unnamed or named.
32871 * <div ui-view></div>
32874 * <div ui-view="viewName"></div>
32877 * You can only have one unnamed view within any template (or root html). If you are only using a
32878 * single view and it is unnamed then you can populate it like so:
32880 * <div ui-view></div>
32881 * $stateProvider.state("home", {
32882 * template: "<h1>HELLO!</h1>"
32886 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
32887 * config property, by name, in this case an empty name:
32889 * $stateProvider.state("home", {
32892 * template: "<h1>HELLO!</h1>"
32898 * But typically you'll only use the views property if you name your view or have more than one view
32899 * in the same template. There's not really a compelling reason to name a view if its the only one,
32900 * but you could if you wanted, like so:
32902 * <div ui-view="main"></div>
32905 * $stateProvider.state("home", {
32908 * template: "<h1>HELLO!</h1>"
32914 * Really though, you'll use views to set up multiple views:
32916 * <div ui-view></div>
32917 * <div ui-view="chart"></div>
32918 * <div ui-view="data"></div>
32922 * $stateProvider.state("home", {
32925 * template: "<h1>HELLO!</h1>"
32928 * template: "<chart_thing/>"
32931 * template: "<data_thing/>"
32937 * Examples for `autoscroll`:
32940 * <!-- If autoscroll present with no expression,
32941 * then scroll ui-view into view -->
32942 * <ui-view autoscroll/>
32944 * <!-- If autoscroll present with valid expression,
32945 * then scroll ui-view into view if expression evaluates to true -->
32946 * <ui-view autoscroll='true'/>
32947 * <ui-view autoscroll='false'/>
32948 * <ui-view autoscroll='scopeVariable'/>
32951 $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
32952 function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
32954 function getService() {
32955 return ($injector.has) ? function(service) {
32956 return $injector.has(service) ? $injector.get(service) : null;
32957 } : function(service) {
32959 return $injector.get(service);
32966 var service = getService(),
32967 $animator = service('$animator'),
32968 $animate = service('$animate');
32970 // Returns a set of DOM manipulation functions based on which Angular version
32972 function getRenderer(attrs, scope) {
32973 var statics = function() {
32975 enter: function (element, target, cb) { target.after(element); cb(); },
32976 leave: function (element, cb) { element.remove(); cb(); }
32982 enter: function(element, target, cb) {
32983 var promise = $animate.enter(element, null, target, cb);
32984 if (promise && promise.then) promise.then(cb);
32986 leave: function(element, cb) {
32987 var promise = $animate.leave(element, cb);
32988 if (promise && promise.then) promise.then(cb);
32994 var animate = $animator && $animator(scope, attrs);
32997 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
32998 leave: function(element, cb) { animate.leave(element); cb(); }
33009 transclude: 'element',
33010 compile: function (tElement, tAttrs, $transclude) {
33011 return function (scope, $element, attrs) {
33012 var previousEl, currentEl, currentScope, latestLocals,
33013 onloadExp = attrs.onload || '',
33014 autoScrollExp = attrs.autoscroll,
33015 renderer = getRenderer(attrs, scope);
33017 scope.$on('$stateChangeSuccess', function() {
33020 scope.$on('$viewContentLoading', function() {
33026 function cleanupLastView() {
33028 previousEl.remove();
33032 if (currentScope) {
33033 currentScope.$destroy();
33034 currentScope = null;
33038 renderer.leave(currentEl, function() {
33042 previousEl = currentEl;
33047 function updateView(firstTime) {
33049 name = getUiViewName(scope, attrs, $element, $interpolate),
33050 previousLocals = name && $state.$current && $state.$current.locals[name];
33052 if (!firstTime && previousLocals === latestLocals) return; // nothing to do
33053 newScope = scope.$new();
33054 latestLocals = $state.$current.locals[name];
33056 var clone = $transclude(newScope, function(clone) {
33057 renderer.enter(clone, $element, function onUiViewEnter() {
33059 currentScope.$emit('$viewContentAnimationEnded');
33062 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
33063 $uiViewScroll(clone);
33070 currentScope = newScope;
33073 * @name ui.router.state.directive:ui-view#$viewContentLoaded
33074 * @eventOf ui.router.state.directive:ui-view
33075 * @eventType emits on ui-view directive scope
33077 * Fired once the view is **loaded**, *after* the DOM is rendered.
33079 * @param {Object} event Event object.
33081 currentScope.$emit('$viewContentLoaded');
33082 currentScope.$eval(onloadExp);
33091 $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
33092 function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
33096 compile: function (tElement) {
33097 var initial = tElement.html();
33098 return function (scope, $element, attrs) {
33099 var current = $state.$current,
33100 name = getUiViewName(scope, attrs, $element, $interpolate),
33101 locals = current && current.locals[name];
33107 $element.data('$uiView', { name: name, state: locals.$$state });
33108 $element.html(locals.$template ? locals.$template : initial);
33110 var link = $compile($element.contents());
33112 if (locals.$$controller) {
33113 locals.$scope = scope;
33114 locals.$element = $element;
33115 var controller = $controller(locals.$$controller, locals);
33116 if (locals.$$controllerAs) {
33117 scope[locals.$$controllerAs] = controller;
33119 $element.data('$ngControllerController', controller);
33120 $element.children().data('$ngControllerController', controller);
33130 * Shared ui-view code for both directives:
33131 * Given scope, element, and its attributes, return the view's name
33133 function getUiViewName(scope, attrs, element, $interpolate) {
33134 var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
33135 var inherited = element.inheritedData('$uiView');
33136 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
33139 angular.module('ui.router.state').directive('uiView', $ViewDirective);
33140 angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
33142 function parseStateRef(ref, current) {
33143 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
33144 if (preparsed) ref = current + '(' + preparsed[1] + ')';
33145 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
33146 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
33147 return { state: parsed[1], paramExpr: parsed[3] || null };
33150 function stateContext(el) {
33151 var stateData = el.parent().inheritedData('$uiView');
33153 if (stateData && stateData.state && stateData.state.name) {
33154 return stateData.state;
33160 * @name ui.router.state.directive:ui-sref
33162 * @requires ui.router.state.$state
33163 * @requires $timeout
33168 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
33169 * URL, the directive will automatically generate & update the `href` attribute via
33170 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
33171 * the link will trigger a state transition with optional parameters.
33173 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33174 * handled natively by the browser.
33176 * You can also use relative state paths within ui-sref, just like the relative
33177 * paths passed to `$state.go()`. You just need to be aware that the path is relative
33178 * to the state that the link lives in, in other words the state that loaded the
33179 * template containing the link.
33181 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
33182 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
33186 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
33187 * following template:
33189 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
33192 * <li ng-repeat="contact in contacts">
33193 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
33198 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
33200 * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
33203 * <li ng-repeat="contact in contacts">
33204 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
33206 * <li ng-repeat="contact in contacts">
33207 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
33209 * <li ng-repeat="contact in contacts">
33210 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
33214 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
33217 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
33218 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33220 $StateRefDirective.$inject = ['$state', '$timeout'];
33221 function $StateRefDirective($state, $timeout) {
33222 var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
33226 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33227 link: function(scope, element, attrs, uiSrefActive) {
33228 var ref = parseStateRef(attrs.uiSref, $state.current.name);
33229 var params = null, url = null, base = stateContext(element) || $state.$current;
33230 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
33231 var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
33232 'xlink:href' : 'href';
33233 var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
33234 var isForm = element[0].nodeName === "FORM";
33235 var attr = isForm ? "action" : hrefKind, nav = true;
33237 var options = { relative: base, inherit: true };
33238 var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
33240 angular.forEach(allowedOptions, function(option) {
33241 if (option in optionsOverride) {
33242 options[option] = optionsOverride[option];
33246 var update = function(newVal) {
33247 if (newVal) params = angular.copy(newVal);
33250 newHref = $state.href(ref.state, params, options);
33252 var activeDirective = uiSrefActive[1] || uiSrefActive[0];
33253 if (activeDirective) {
33254 activeDirective.$$addStateInfo(ref.state, params);
33256 if (newHref === null) {
33260 attrs.$set(attr, newHref);
33263 if (ref.paramExpr) {
33264 scope.$watch(ref.paramExpr, function(newVal, oldVal) {
33265 if (newVal !== params) update(newVal);
33267 params = angular.copy(scope.$eval(ref.paramExpr));
33271 if (isForm) return;
33273 element.bind("click", function(e) {
33274 var button = e.which || e.button;
33275 if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
33276 // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
33277 var transition = $timeout(function() {
33278 $state.go(ref.state, params, options);
33280 e.preventDefault();
33282 // if the state has no URL, ignore one preventDefault from the <a> directive.
33283 var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
33284 e.preventDefault = function() {
33285 if (ignorePreventDefaultCount-- <= 0)
33286 $timeout.cancel(transition);
33296 * @name ui.router.state.directive:ui-sref-active
33298 * @requires ui.router.state.$state
33299 * @requires ui.router.state.$stateParams
33300 * @requires $interpolate
33305 * A directive working alongside ui-sref to add classes to an element when the
33306 * related ui-sref directive's state is active, and removing them when it is inactive.
33307 * The primary use-case is to simplify the special appearance of navigation menus
33308 * relying on `ui-sref`, by having the "active" state's menu button appear different,
33309 * distinguishing it from the inactive menu items.
33311 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
33312 * ui-sref-active found at the same level or above the ui-sref will be used.
33314 * Will activate when the ui-sref's target state or any child state is active. If you
33315 * need to activate only when the ui-sref target state is active and *not* any of
33316 * it's children, then you will use
33317 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
33320 * Given the following template:
33323 * <li ui-sref-active="active" class="item">
33324 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
33330 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
33331 * the resulting HTML will appear as (note the 'active' class):
33334 * <li ui-sref-active="active" class="item active">
33335 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
33340 * The class name is interpolated **once** during the directives link time (any further changes to the
33341 * interpolated value are ignored).
33343 * Multiple classes may be specified in a space-separated format:
33346 * <li ui-sref-active='class1 class2 class3'>
33347 * <a ui-sref="app.user">link</a>
33355 * @name ui.router.state.directive:ui-sref-active-eq
33357 * @requires ui.router.state.$state
33358 * @requires ui.router.state.$stateParams
33359 * @requires $interpolate
33364 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
33365 * when the exact target state used in the `ui-sref` is active; no child states.
33368 $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
33369 function $StateRefActiveDirective($state, $stateParams, $interpolate) {
33372 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
33373 var states = [], activeClass;
33375 // There probably isn't much point in $observing this
33376 // uiSrefActive and uiSrefActiveEq share the same directive object with some
33377 // slight difference in logic routing
33378 activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
33380 // Allow uiSref to communicate with uiSrefActive[Equals]
33381 this.$$addStateInfo = function (newState, newParams) {
33382 var state = $state.get(newState, stateContext($element));
33385 state: state || { name: newState },
33392 $scope.$on('$stateChangeSuccess', update);
33394 // Update route state
33395 function update() {
33397 $element.addClass(activeClass);
33399 $element.removeClass(activeClass);
33403 function anyMatch() {
33404 for (var i = 0; i < states.length; i++) {
33405 if (isMatch(states[i].state, states[i].params)) {
33412 function isMatch(state, params) {
33413 if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
33414 return $state.is(state.name, params);
33416 return $state.includes(state.name, params);
33423 angular.module('ui.router.state')
33424 .directive('uiSref', $StateRefDirective)
33425 .directive('uiSrefActive', $StateRefActiveDirective)
33426 .directive('uiSrefActiveEq', $StateRefActiveDirective);
33430 * @name ui.router.state.filter:isState
33432 * @requires ui.router.state.$state
33435 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
33437 $IsStateFilter.$inject = ['$state'];
33438 function $IsStateFilter($state) {
33439 var isFilter = function (state) {
33440 return $state.is(state);
33442 isFilter.$stateful = true;
33448 * @name ui.router.state.filter:includedByState
33450 * @requires ui.router.state.$state
33453 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
33455 $IncludedByStateFilter.$inject = ['$state'];
33456 function $IncludedByStateFilter($state) {
33457 var includesFilter = function (state) {
33458 return $state.includes(state);
33460 includesFilter.$stateful = true;
33461 return includesFilter;
33464 angular.module('ui.router.state')
33465 .filter('isState', $IsStateFilter)
33466 .filter('includedByState', $IncludedByStateFilter);
33467 })(window, window.angular);
33471 /***/ function(module, exports, __webpack_require__) {
33473 __webpack_require__(5);
33474 module.exports = 'ngResource';
33479 /***/ function(module, exports) {
33482 * @license AngularJS v1.4.8
33483 * (c) 2010-2015 Google, Inc. http://angularjs.org
33486 (function(window, angular, undefined) {'use strict';
33488 var $resourceMinErr = angular.$$minErr('$resource');
33490 // Helper functions and regex to lookup a dotted path on an object
33491 // stopping at undefined/null. The path must be composed of ASCII
33492 // identifiers (just like $parse)
33493 var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
33495 function isValidDottedPath(path) {
33496 return (path != null && path !== '' && path !== 'hasOwnProperty' &&
33497 MEMBER_NAME_REGEX.test('.' + path));
33500 function lookupDottedPath(obj, path) {
33501 if (!isValidDottedPath(path)) {
33502 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
33504 var keys = path.split('.');
33505 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
33507 obj = (obj !== null) ? obj[key] : undefined;
33513 * Create a shallow copy of an object and clear other fields from the destination
33515 function shallowClearAndCopy(src, dst) {
33518 angular.forEach(dst, function(value, key) {
33522 for (var key in src) {
33523 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
33524 dst[key] = src[key];
33538 * The `ngResource` module provides interaction support with RESTful services
33539 * via the $resource service.
33542 * <div doc-module-components="ngResource"></div>
33544 * See {@link ngResource.$resource `$resource`} for usage.
33553 * A factory which creates a resource object that lets you interact with
33554 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
33556 * The returned resource object has action methods which provide high-level behaviors without
33557 * the need to interact with the low level {@link ng.$http $http} service.
33559 * Requires the {@link ngResource `ngResource`} module to be installed.
33561 * By default, trailing slashes will be stripped from the calculated URLs,
33562 * which can pose problems with server backends that do not expect that
33563 * behavior. This can be disabled by configuring the `$resourceProvider` like
33567 app.config(['$resourceProvider', function($resourceProvider) {
33568 // Don't strip trailing slashes from calculated URLs
33569 $resourceProvider.defaults.stripTrailingSlashes = false;
33573 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
33574 * `/user/:username`. If you are using a URL with a port number (e.g.
33575 * `http://example.com:8080/api`), it will be respected.
33577 * If you are using a url with a suffix, just add the suffix, like this:
33578 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
33579 * or even `$resource('http://example.com/resource/:resource_id.:format')`
33580 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
33581 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
33582 * can escape it with `/\.`.
33584 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33585 * `actions` methods. If any of the parameter value is a function, it will be executed every time
33586 * when a param value needs to be obtained for a request (unless the param was overridden).
33588 * Each key value in the parameter object is first bound to url template if present and then any
33589 * excess keys are appended to the url search query after the `?`.
33591 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
33592 * URL `/path/greet?salutation=Hello`.
33594 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
33595 * from the corresponding property on the `data` object (provided when calling an action method). For
33596 * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
33597 * will be `data.someProp`.
33599 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
33600 * the default set of resource actions. The declaration should be created in the format of {@link
33601 * ng.$http#usage $http.config}:
33603 * {action1: {method:?, params:?, isArray:?, headers:?, ...},
33604 * action2: {method:?, params:?, isArray:?, headers:?, ...},
33609 * - **`action`** – {string} – The name of action. This name becomes the name of the method on
33610 * your resource object.
33611 * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
33612 * `DELETE`, `JSONP`, etc).
33613 * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
33614 * the parameter value is a function, it will be executed every time when a param value needs to
33615 * be obtained for a request (unless the param was overridden).
33616 * - **`url`** – {string} – action specific `url` override. The url templating is supported just
33617 * like for the resource-level urls.
33618 * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
33619 * see `returns` section.
33620 * - **`transformRequest`** –
33621 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33622 * transform function or an array of such functions. The transform function takes the http
33623 * request body and headers and returns its transformed (typically serialized) version.
33624 * By default, transformRequest will contain one function that checks if the request data is
33625 * an object and serializes to using `angular.toJson`. To prevent this behavior, set
33626 * `transformRequest` to an empty array: `transformRequest: []`
33627 * - **`transformResponse`** –
33628 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33629 * transform function or an array of such functions. The transform function takes the http
33630 * response body and headers and returns its transformed (typically deserialized) version.
33631 * By default, transformResponse will contain one function that checks if the response looks like
33632 * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
33633 * `transformResponse` to an empty array: `transformResponse: []`
33634 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
33635 * GET request, otherwise if a cache instance built with
33636 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
33638 * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
33639 * should abort the request when resolved.
33640 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
33642 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
33643 * for more information.
33644 * - **`responseType`** - `{string}` - see
33645 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
33646 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
33647 * `response` and `responseError`. Both `response` and `responseError` interceptors get called
33648 * with `http response` object. See {@link ng.$http $http interceptors}.
33650 * @param {Object} options Hash with custom settings that should extend the
33651 * default `$resourceProvider` behavior. The only supported option is
33655 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
33656 * slashes from any calculated URL will be stripped. (Defaults to true.)
33658 * @returns {Object} A resource "class" object with methods for the default set of resource actions
33659 * optionally extended with custom `actions`. The default set contains these actions:
33661 * { 'get': {method:'GET'},
33662 * 'save': {method:'POST'},
33663 * 'query': {method:'GET', isArray:true},
33664 * 'remove': {method:'DELETE'},
33665 * 'delete': {method:'DELETE'} };
33668 * Calling these methods invoke an {@link ng.$http} with the specified http method,
33669 * destination and parameters. When the data is returned from the server then the object is an
33670 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
33671 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
33672 * read, update, delete) on server-side data like this:
33674 * var User = $resource('/user/:userId', {userId:'@id'});
33675 * var user = User.get({userId:123}, function() {
33681 * It is important to realize that invoking a $resource object method immediately returns an
33682 * empty reference (object or array depending on `isArray`). Once the data is returned from the
33683 * server the existing reference is populated with the actual data. This is a useful trick since
33684 * usually the resource is assigned to a model which is then rendered by the view. Having an empty
33685 * object results in no rendering, once the data arrives from the server then the object is
33686 * populated with the data and the view automatically re-renders itself showing the new data. This
33687 * means that in most cases one never has to write a callback function for the action methods.
33689 * The action methods on the class object or instance object can be invoked with the following
33692 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
33693 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
33694 * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
33697 * Success callback is called with (value, responseHeaders) arguments, where the value is
33698 * the populated resource instance or collection object. The error callback is called
33699 * with (httpResponse) argument.
33701 * Class actions return empty instance (with additional properties below).
33702 * Instance actions return promise of the action.
33704 * The Resource instances and collection have these additional properties:
33706 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
33707 * instance or collection.
33709 * On success, the promise is resolved with the same resource instance or collection object,
33710 * updated with data from server. This makes it easy to use in
33711 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
33712 * rendering until the resource(s) are loaded.
33714 * On failure, the promise is resolved with the {@link ng.$http http response} object, without
33715 * the `resource` property.
33717 * If an interceptor object was provided, the promise will instead be resolved with the value
33718 * returned by the interceptor.
33720 * - `$resolved`: `true` after first server interaction is completed (either with success or
33721 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
33726 * # Credit card resource
33729 // Define CreditCard class
33730 var CreditCard = $resource('/user/:userId/card/:cardId',
33731 {userId:123, cardId:'@id'}, {
33732 charge: {method:'POST', params:{charge:true}}
33735 // We can retrieve a collection from the server
33736 var cards = CreditCard.query(function() {
33737 // GET: /user/123/card
33738 // server returns: [ {id:456, number:'1234', name:'Smith'} ];
33740 var card = cards[0];
33741 // each item is an instance of CreditCard
33742 expect(card instanceof CreditCard).toEqual(true);
33743 card.name = "J. Smith";
33744 // non GET methods are mapped onto the instances
33746 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
33747 // server returns: {id:456, number:'1234', name: 'J. Smith'};
33749 // our custom method is mapped as well.
33750 card.$charge({amount:9.99});
33751 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
33754 // we can create an instance as well
33755 var newCard = new CreditCard({number:'0123'});
33756 newCard.name = "Mike Smith";
33758 // POST: /user/123/card {number:'0123', name:'Mike Smith'}
33759 // server returns: {id:789, number:'0123', name: 'Mike Smith'};
33760 expect(newCard.id).toEqual(789);
33763 * The object returned from this function execution is a resource "class" which has "static" method
33764 * for each action in the definition.
33766 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
33768 * When the data is returned from the server then the object is an instance of the resource type and
33769 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
33770 * operations (create, read, update, delete) on server-side data.
33773 var User = $resource('/user/:userId', {userId:'@id'});
33774 User.get({userId:123}, function(user) {
33780 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
33781 * in the response that came from the server as well as $http header getter function, so one
33782 * could rewrite the above example and get access to http headers as:
33785 var User = $resource('/user/:userId', {userId:'@id'});
33786 User.get({userId:123}, function(u, getResponseHeaders){
33788 u.$save(function(u, putResponseHeaders) {
33789 //u => saved user object
33790 //putResponseHeaders => $http header getter
33795 * You can also access the raw `$http` promise via the `$promise` property on the object returned
33798 var User = $resource('/user/:userId', {userId:'@id'});
33799 User.get({userId:123})
33800 .$promise.then(function(user) {
33801 $scope.user = user;
33805 * # Creating a custom 'PUT' request
33806 * In this example we create a custom method on our resource to make a PUT request
33808 * var app = angular.module('app', ['ngResource', 'ngRoute']);
33810 * // Some APIs expect a PUT request in the format URL/object/ID
33811 * // Here we are creating an 'update' method
33812 * app.factory('Notes', ['$resource', function($resource) {
33813 * return $resource('/notes/:id', null,
33815 * 'update': { method:'PUT' }
33819 * // In our controller we get the ID from the URL using ngRoute and $routeParams
33820 * // We pass in $routeParams and our Notes factory along with $scope
33821 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
33822 function($scope, $routeParams, Notes) {
33823 * // First get a note object from the factory
33824 * var note = Notes.get({ id:$routeParams.id });
33827 * // Now call update passing in the ID first then the object you are updating
33828 * Notes.update({ id:$id }, note);
33830 * // This will PUT /notes/ID with the note object in the request payload
33834 angular.module('ngResource', ['ng']).
33835 provider('$resource', function() {
33836 var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
33837 var provider = this;
33840 // Strip slashes by default
33841 stripTrailingSlashes: true,
33843 // Default actions configuration
33845 'get': {method: 'GET'},
33846 'save': {method: 'POST'},
33847 'query': {method: 'GET', isArray: true},
33848 'remove': {method: 'DELETE'},
33849 'delete': {method: 'DELETE'}
33853 this.$get = ['$http', '$q', function($http, $q) {
33855 var noop = angular.noop,
33856 forEach = angular.forEach,
33857 extend = angular.extend,
33858 copy = angular.copy,
33859 isFunction = angular.isFunction;
33862 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
33863 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
33864 * (pchar) allowed in path segments:
33866 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33867 * pct-encoded = "%" HEXDIG HEXDIG
33868 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33869 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33870 * / "*" / "+" / "," / ";" / "="
33872 function encodeUriSegment(val) {
33873 return encodeUriQuery(val, true).
33874 replace(/%26/gi, '&').
33875 replace(/%3D/gi, '=').
33876 replace(/%2B/gi, '+');
33881 * This method is intended for encoding *key* or *value* parts of query component. We need a
33882 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
33883 * have to be encoded per http://tools.ietf.org/html/rfc3986:
33884 * query = *( pchar / "/" / "?" )
33885 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33886 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33887 * pct-encoded = "%" HEXDIG HEXDIG
33888 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33889 * / "*" / "+" / "," / ";" / "="
33891 function encodeUriQuery(val, pctEncodeSpaces) {
33892 return encodeURIComponent(val).
33893 replace(/%40/gi, '@').
33894 replace(/%3A/gi, ':').
33895 replace(/%24/g, '$').
33896 replace(/%2C/gi, ',').
33897 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
33900 function Route(template, defaults) {
33901 this.template = template;
33902 this.defaults = extend({}, provider.defaults, defaults);
33903 this.urlParams = {};
33906 Route.prototype = {
33907 setUrlParams: function(config, params, actionUrl) {
33909 url = actionUrl || self.template,
33912 protocolAndDomain = '';
33914 var urlParams = self.urlParams = {};
33915 forEach(url.split(/\W/), function(param) {
33916 if (param === 'hasOwnProperty') {
33917 throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
33919 if (!(new RegExp("^\\d+$").test(param)) && param &&
33920 (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
33921 urlParams[param] = true;
33924 url = url.replace(/\\:/g, ':');
33925 url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
33926 protocolAndDomain = match;
33930 params = params || {};
33931 forEach(self.urlParams, function(_, urlParam) {
33932 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
33933 if (angular.isDefined(val) && val !== null) {
33934 encodedVal = encodeUriSegment(val);
33935 url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
33936 return encodedVal + p1;
33939 url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
33940 leadingSlashes, tail) {
33941 if (tail.charAt(0) == '/') {
33944 return leadingSlashes + tail;
33950 // strip trailing slashes and set the url (unless this behavior is specifically disabled)
33951 if (self.defaults.stripTrailingSlashes) {
33952 url = url.replace(/\/+$/, '') || '/';
33955 // then replace collapse `/.` if found in the last URL path segment before the query
33956 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
33957 url = url.replace(/\/\.(?=\w+($|\?))/, '.');
33958 // replace escaped `/\.` with `/.`
33959 config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
33962 // set params - delegate param encoding to $http
33963 forEach(params, function(value, key) {
33964 if (!self.urlParams[key]) {
33965 config.params = config.params || {};
33966 config.params[key] = value;
33973 function resourceFactory(url, paramDefaults, actions, options) {
33974 var route = new Route(url, options);
33976 actions = extend({}, provider.defaults.actions, actions);
33978 function extractParams(data, actionParams) {
33980 actionParams = extend({}, paramDefaults, actionParams);
33981 forEach(actionParams, function(value, key) {
33982 if (isFunction(value)) { value = value(); }
33983 ids[key] = value && value.charAt && value.charAt(0) == '@' ?
33984 lookupDottedPath(data, value.substr(1)) : value;
33989 function defaultResponseInterceptor(response) {
33990 return response.resource;
33993 function Resource(value) {
33994 shallowClearAndCopy(value || {}, this);
33997 Resource.prototype.toJSON = function() {
33998 var data = extend({}, this);
33999 delete data.$promise;
34000 delete data.$resolved;
34004 forEach(actions, function(action, name) {
34005 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
34007 Resource[name] = function(a1, a2, a3, a4) {
34008 var params = {}, data, success, error;
34010 /* jshint -W086 */ /* (purposefully fall through case statements) */
34011 switch (arguments.length) {
34018 if (isFunction(a2)) {
34019 if (isFunction(a1)) {
34035 if (isFunction(a1)) success = a1;
34036 else if (hasBody) data = a1;
34041 throw $resourceMinErr('badargs',
34042 "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
34045 /* jshint +W086 */ /* (purposefully fall through case statements) */
34047 var isInstanceCall = this instanceof Resource;
34048 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
34049 var httpConfig = {};
34050 var responseInterceptor = action.interceptor && action.interceptor.response ||
34051 defaultResponseInterceptor;
34052 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
34055 forEach(action, function(value, key) {
34058 httpConfig[key] = copy(value);
34062 case 'interceptor':
34065 httpConfig[key] = value;
34070 if (hasBody) httpConfig.data = data;
34071 route.setUrlParams(httpConfig,
34072 extend({}, extractParams(data, action.params || {}), params),
34075 var promise = $http(httpConfig).then(function(response) {
34076 var data = response.data,
34077 promise = value.$promise;
34080 // Need to convert action.isArray to boolean in case it is undefined
34082 if (angular.isArray(data) !== (!!action.isArray)) {
34083 throw $resourceMinErr('badcfg',
34084 'Error in resource configuration for action `{0}`. Expected response to ' +
34085 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
34086 angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
34089 if (action.isArray) {
34091 forEach(data, function(item) {
34092 if (typeof item === "object") {
34093 value.push(new Resource(item));
34095 // Valid JSON values may be string literals, and these should not be converted
34096 // into objects. These items will not have access to the Resource prototype
34097 // methods, but unfortunately there
34102 shallowClearAndCopy(data, value);
34103 value.$promise = promise;
34107 value.$resolved = true;
34109 response.resource = value;
34112 }, function(response) {
34113 value.$resolved = true;
34115 (error || noop)(response);
34117 return $q.reject(response);
34120 promise = promise.then(
34121 function(response) {
34122 var value = responseInterceptor(response);
34123 (success || noop)(value, response.headers);
34126 responseErrorInterceptor);
34128 if (!isInstanceCall) {
34129 // we are creating instance / collection
34130 // - set the initial promise
34131 // - return the instance / collection
34132 value.$promise = promise;
34133 value.$resolved = false;
34143 Resource.prototype['$' + name] = function(params, success, error) {
34144 if (isFunction(params)) {
34145 error = success; success = params; params = {};
34147 var result = Resource[name].call(this, params, this, success, error);
34148 return result.$promise || result;
34152 Resource.bind = function(additionalParamDefaults) {
34153 return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
34159 return resourceFactory;
34164 })(window, window.angular);
34169 /***/ function(module, exports, __webpack_require__) {
34171 __webpack_require__(7);
34173 module.exports = 'ui.bootstrap';
34178 /***/ function(module, exports) {
34181 * angular-ui-bootstrap
34182 * http://angular-ui.github.io/bootstrap/
34184 * Version: 1.0.0 - 2016-01-08
34187 angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
34188 angular.module("ui.bootstrap.tpls", ["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/popup.html","uib/template/datepicker/year.html","uib/template/modal/backdrop.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]);
34189 angular.module('ui.bootstrap.collapse', [])
34191 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
34192 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
34194 link: function(scope, element, attrs) {
34195 if (!scope.$eval(attrs.uibCollapse)) {
34196 element.addClass('in')
34197 .addClass('collapse')
34198 .css({height: 'auto'});
34201 function expand() {
34202 element.removeClass('collapse')
34203 .addClass('collapsing')
34204 .attr('aria-expanded', true)
34205 .attr('aria-hidden', false);
34208 $animateCss(element, {
34211 to: { height: element[0].scrollHeight + 'px' }
34212 }).start()['finally'](expandDone);
34214 $animate.addClass(element, 'in', {
34215 to: { height: element[0].scrollHeight + 'px' }
34216 }).then(expandDone);
34220 function expandDone() {
34221 element.removeClass('collapsing')
34222 .addClass('collapse')
34223 .css({height: 'auto'});
34226 function collapse() {
34227 if (!element.hasClass('collapse') && !element.hasClass('in')) {
34228 return collapseDone();
34232 // IMPORTANT: The height must be set before adding "collapsing" class.
34233 // Otherwise, the browser attempts to animate from height 0 (in
34234 // collapsing class) to the given height here.
34235 .css({height: element[0].scrollHeight + 'px'})
34236 // initially all panel collapse have the collapse class, this removal
34237 // prevents the animation from jumping to collapsed state
34238 .removeClass('collapse')
34239 .addClass('collapsing')
34240 .attr('aria-expanded', false)
34241 .attr('aria-hidden', true);
34244 $animateCss(element, {
34247 }).start()['finally'](collapseDone);
34249 $animate.removeClass(element, 'in', {
34251 }).then(collapseDone);
34255 function collapseDone() {
34256 element.css({height: '0'}); // Required so that collapse works when animation is disabled
34257 element.removeClass('collapsing')
34258 .addClass('collapse');
34261 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
34262 if (shouldCollapse) {
34272 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
34274 .constant('uibAccordionConfig', {
34278 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
34279 // This array keeps track of the accordion groups
34282 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
34283 this.closeOthers = function(openGroup) {
34284 var closeOthers = angular.isDefined($attrs.closeOthers) ?
34285 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
34287 angular.forEach(this.groups, function(group) {
34288 if (group !== openGroup) {
34289 group.isOpen = false;
34295 // This is called from the accordion-group directive to add itself to the accordion
34296 this.addGroup = function(groupScope) {
34298 this.groups.push(groupScope);
34300 groupScope.$on('$destroy', function(event) {
34301 that.removeGroup(groupScope);
34305 // This is called from the accordion-group directive when to remove itself
34306 this.removeGroup = function(group) {
34307 var index = this.groups.indexOf(group);
34308 if (index !== -1) {
34309 this.groups.splice(index, 1);
34314 // The accordion directive simply sets up the directive controller
34315 // and adds an accordion CSS class to itself element.
34316 .directive('uibAccordion', function() {
34318 controller: 'UibAccordionController',
34319 controllerAs: 'accordion',
34321 templateUrl: function(element, attrs) {
34322 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
34327 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
34328 .directive('uibAccordionGroup', function() {
34330 require: '^uibAccordion', // We need this directive to be inside an accordion
34331 transclude: true, // It transcludes the contents of the directive into the template
34332 replace: true, // The element containing the directive will be replaced with the template
34333 templateUrl: function(element, attrs) {
34334 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
34337 heading: '@', // Interpolate the heading attribute onto this scope
34341 controller: function() {
34342 this.setHeading = function(element) {
34343 this.heading = element;
34346 link: function(scope, element, attrs, accordionCtrl) {
34347 accordionCtrl.addGroup(scope);
34349 scope.openClass = attrs.openClass || 'panel-open';
34350 scope.panelClass = attrs.panelClass || 'panel-default';
34351 scope.$watch('isOpen', function(value) {
34352 element.toggleClass(scope.openClass, !!value);
34354 accordionCtrl.closeOthers(scope);
34358 scope.toggleOpen = function($event) {
34359 if (!scope.isDisabled) {
34360 if (!$event || $event.which === 32) {
34361 scope.isOpen = !scope.isOpen;
34369 // Use accordion-heading below an accordion-group to provide a heading containing HTML
34370 .directive('uibAccordionHeading', function() {
34372 transclude: true, // Grab the contents to be used as the heading
34373 template: '', // In effect remove this element!
34375 require: '^uibAccordionGroup',
34376 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
34377 // Pass the heading to the accordion-group controller
34378 // so that it can be transcluded into the right place in the template
34379 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
34380 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
34385 // Use in the accordion-group template to indicate where you want the heading to be transcluded
34386 // You must provide the property on the accordion-group controller that will hold the transcluded element
34387 .directive('uibAccordionTransclude', function() {
34389 require: '^uibAccordionGroup',
34390 link: function(scope, element, attrs, controller) {
34391 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
34393 element.find('span').html('');
34394 element.find('span').append(heading);
34401 angular.module('ui.bootstrap.alert', [])
34403 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
34404 $scope.closeable = !!$attrs.close;
34406 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
34407 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
34409 if (dismissOnTimeout) {
34410 $timeout(function() {
34412 }, parseInt(dismissOnTimeout, 10));
34416 .directive('uibAlert', function() {
34418 controller: 'UibAlertController',
34419 controllerAs: 'alert',
34420 templateUrl: function(element, attrs) {
34421 return attrs.templateUrl || 'uib/template/alert/alert.html';
34432 angular.module('ui.bootstrap.buttons', [])
34434 .constant('uibButtonConfig', {
34435 activeClass: 'active',
34436 toggleEvent: 'click'
34439 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
34440 this.activeClass = buttonConfig.activeClass || 'active';
34441 this.toggleEvent = buttonConfig.toggleEvent || 'click';
34444 .directive('uibBtnRadio', ['$parse', function($parse) {
34446 require: ['uibBtnRadio', 'ngModel'],
34447 controller: 'UibButtonsController',
34448 controllerAs: 'buttons',
34449 link: function(scope, element, attrs, ctrls) {
34450 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34451 var uncheckableExpr = $parse(attrs.uibUncheckable);
34453 element.find('input').css({display: 'none'});
34456 ngModelCtrl.$render = function() {
34457 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
34461 element.on(buttonsCtrl.toggleEvent, function() {
34462 if (attrs.disabled) {
34466 var isActive = element.hasClass(buttonsCtrl.activeClass);
34468 if (!isActive || angular.isDefined(attrs.uncheckable)) {
34469 scope.$apply(function() {
34470 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
34471 ngModelCtrl.$render();
34476 if (attrs.uibUncheckable) {
34477 scope.$watch(uncheckableExpr, function(uncheckable) {
34478 attrs.$set('uncheckable', uncheckable ? '' : null);
34485 .directive('uibBtnCheckbox', function() {
34487 require: ['uibBtnCheckbox', 'ngModel'],
34488 controller: 'UibButtonsController',
34489 controllerAs: 'button',
34490 link: function(scope, element, attrs, ctrls) {
34491 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34493 element.find('input').css({display: 'none'});
34495 function getTrueValue() {
34496 return getCheckboxValue(attrs.btnCheckboxTrue, true);
34499 function getFalseValue() {
34500 return getCheckboxValue(attrs.btnCheckboxFalse, false);
34503 function getCheckboxValue(attribute, defaultValue) {
34504 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
34508 ngModelCtrl.$render = function() {
34509 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
34513 element.on(buttonsCtrl.toggleEvent, function() {
34514 if (attrs.disabled) {
34518 scope.$apply(function() {
34519 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
34520 ngModelCtrl.$render();
34527 angular.module('ui.bootstrap.carousel', [])
34529 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
34531 slides = self.slides = $scope.slides = [],
34532 SLIDE_DIRECTION = 'uib-slideDirection',
34534 currentInterval, isPlaying, bufferedTransitions = [];
34535 self.currentSlide = null;
34537 var destroyed = false;
34539 self.addSlide = function(slide, element) {
34540 slide.$element = element;
34541 slides.push(slide);
34542 //if this is the first slide or the slide is set to active, select it
34543 if (slides.length === 1 || slide.active) {
34544 if ($scope.$currentTransition) {
34545 $scope.$currentTransition = null;
34548 self.select(slides[slides.length - 1]);
34549 if (slides.length === 1) {
34553 slide.active = false;
34557 self.getCurrentIndex = function() {
34558 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
34559 return +self.currentSlide.index;
34561 return currentIndex;
34564 self.next = $scope.next = function() {
34565 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
34567 if (newIndex === 0 && $scope.noWrap()) {
34572 return self.select(getSlideByIndex(newIndex), 'next');
34575 self.prev = $scope.prev = function() {
34576 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
34578 if ($scope.noWrap() && newIndex === slides.length - 1) {
34583 return self.select(getSlideByIndex(newIndex), 'prev');
34586 self.removeSlide = function(slide) {
34587 if (angular.isDefined(slide.index)) {
34588 slides.sort(function(a, b) {
34589 return +a.index > +b.index;
34593 var bufferedIndex = bufferedTransitions.indexOf(slide);
34594 if (bufferedIndex !== -1) {
34595 bufferedTransitions.splice(bufferedIndex, 1);
34597 //get the index of the slide inside the carousel
34598 var index = slides.indexOf(slide);
34599 slides.splice(index, 1);
34600 $timeout(function() {
34601 if (slides.length > 0 && slide.active) {
34602 if (index >= slides.length) {
34603 self.select(slides[index - 1]);
34605 self.select(slides[index]);
34607 } else if (currentIndex > index) {
34612 //clean the currentSlide when no more slide
34613 if (slides.length === 0) {
34614 self.currentSlide = null;
34615 clearBufferedTransitions();
34619 /* direction: "prev" or "next" */
34620 self.select = $scope.select = function(nextSlide, direction) {
34621 var nextIndex = $scope.indexOfSlide(nextSlide);
34622 //Decide direction if it's not given
34623 if (direction === undefined) {
34624 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34626 //Prevent this user-triggered transition from occurring if there is already one in progress
34627 if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
34628 goNext(nextSlide, nextIndex, direction);
34629 } else if (nextSlide && nextSlide !== self.currentSlide && $scope.$currentTransition) {
34630 bufferedTransitions.push(nextSlide);
34634 /* Allow outside people to call indexOf on slides array */
34635 $scope.indexOfSlide = function(slide) {
34636 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
34639 $scope.isActive = function(slide) {
34640 return self.currentSlide === slide;
34643 $scope.pause = function() {
34644 if (!$scope.noPause) {
34650 $scope.play = function() {
34657 $scope.$on('$destroy', function() {
34662 $scope.$watch('noTransition', function(noTransition) {
34663 $animate.enabled($element, !noTransition);
34666 $scope.$watch('interval', restartTimer);
34668 $scope.$watchCollection('slides', resetTransition);
34670 function clearBufferedTransitions() {
34671 while (bufferedTransitions.length) {
34672 bufferedTransitions.shift();
34676 function getSlideByIndex(index) {
34677 if (angular.isUndefined(slides[index].index)) {
34678 return slides[index];
34680 for (var i = 0, l = slides.length; i < l; ++i) {
34681 if (slides[i].index === index) {
34687 function goNext(slide, index, direction) {
34688 if (destroyed) { return; }
34690 angular.extend(slide, {direction: direction, active: true});
34691 angular.extend(self.currentSlide || {}, {direction: direction, active: false});
34692 if ($animate.enabled($element) && !$scope.$currentTransition &&
34693 slide.$element && self.slides.length > 1) {
34694 slide.$element.data(SLIDE_DIRECTION, slide.direction);
34695 if (self.currentSlide && self.currentSlide.$element) {
34696 self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
34699 $scope.$currentTransition = true;
34700 $animate.on('addClass', slide.$element, function(element, phase) {
34701 if (phase === 'close') {
34702 $scope.$currentTransition = null;
34703 $animate.off('addClass', element);
34704 if (bufferedTransitions.length) {
34705 var nextSlide = bufferedTransitions.pop();
34706 var nextIndex = $scope.indexOfSlide(nextSlide);
34707 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34708 clearBufferedTransitions();
34710 goNext(nextSlide, nextIndex, nextDirection);
34716 self.currentSlide = slide;
34717 currentIndex = index;
34719 //every time you change slides, reset the timer
34723 function resetTimer() {
34724 if (currentInterval) {
34725 $interval.cancel(currentInterval);
34726 currentInterval = null;
34730 function resetTransition(slides) {
34731 if (!slides.length) {
34732 $scope.$currentTransition = null;
34733 clearBufferedTransitions();
34737 function restartTimer() {
34739 var interval = +$scope.interval;
34740 if (!isNaN(interval) && interval > 0) {
34741 currentInterval = $interval(timerFn, interval);
34745 function timerFn() {
34746 var interval = +$scope.interval;
34747 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
34755 .directive('uibCarousel', function() {
34759 controller: 'UibCarouselController',
34760 controllerAs: 'carousel',
34761 templateUrl: function(element, attrs) {
34762 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
34773 .directive('uibSlide', function() {
34775 require: '^uibCarousel',
34778 templateUrl: function(element, attrs) {
34779 return attrs.templateUrl || 'uib/template/carousel/slide.html';
34786 link: function (scope, element, attrs, carouselCtrl) {
34787 carouselCtrl.addSlide(scope, element);
34788 //when the scope is destroyed then remove the slide from the current slides array
34789 scope.$on('$destroy', function() {
34790 carouselCtrl.removeSlide(scope);
34793 scope.$watch('active', function(active) {
34795 carouselCtrl.select(scope);
34802 .animation('.item', ['$animateCss',
34803 function($animateCss) {
34804 var SLIDE_DIRECTION = 'uib-slideDirection';
34806 function removeClass(element, className, callback) {
34807 element.removeClass(className);
34814 beforeAddClass: function(element, className, done) {
34815 if (className === 'active') {
34816 var stopped = false;
34817 var direction = element.data(SLIDE_DIRECTION);
34818 var directionClass = direction === 'next' ? 'left' : 'right';
34819 var removeClassFn = removeClass.bind(this, element,
34820 directionClass + ' ' + direction, done);
34821 element.addClass(direction);
34823 $animateCss(element, {addClass: directionClass})
34825 .done(removeClassFn);
34827 return function() {
34833 beforeRemoveClass: function (element, className, done) {
34834 if (className === 'active') {
34835 var stopped = false;
34836 var direction = element.data(SLIDE_DIRECTION);
34837 var directionClass = direction === 'next' ? 'left' : 'right';
34838 var removeClassFn = removeClass.bind(this, element, directionClass, done);
34840 $animateCss(element, {addClass: directionClass})
34842 .done(removeClassFn);
34844 return function() {
34853 angular.module('ui.bootstrap.dateparser', [])
34855 .service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
34856 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
34857 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
34860 var formatCodeToRegex;
34862 this.init = function() {
34863 localeId = $locale.id;
34867 formatCodeToRegex = [
34871 apply: function(value) { this.year = +value; }
34876 apply: function(value) { this.year = +value + 2000; }
34881 apply: function(value) { this.year = +value; }
34885 regex: '0?[1-9]|1[0-2]',
34886 apply: function(value) { this.month = value - 1; }
34890 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
34891 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
34895 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
34896 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
34900 regex: '0[1-9]|1[0-2]',
34901 apply: function(value) { this.month = value - 1; }
34905 regex: '[1-9]|1[0-2]',
34906 apply: function(value) { this.month = value - 1; }
34910 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
34911 apply: function(value) { this.date = +value; }
34915 regex: '[0-2][0-9]{1}|3[0-1]{1}',
34916 apply: function(value) { this.date = +value; }
34920 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
34921 apply: function(value) { this.date = +value; }
34925 regex: $locale.DATETIME_FORMATS.DAY.join('|')
34929 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
34933 regex: '(?:0|1)[0-9]|2[0-3]',
34934 apply: function(value) { this.hours = +value; }
34938 regex: '0[0-9]|1[0-2]',
34939 apply: function(value) { this.hours = +value; }
34943 regex: '1?[0-9]|2[0-3]',
34944 apply: function(value) { this.hours = +value; }
34948 regex: '[0-9]|1[0-2]',
34949 apply: function(value) { this.hours = +value; }
34953 regex: '[0-5][0-9]',
34954 apply: function(value) { this.minutes = +value; }
34958 regex: '[0-9]|[1-5][0-9]',
34959 apply: function(value) { this.minutes = +value; }
34963 regex: '[0-9][0-9][0-9]',
34964 apply: function(value) { this.milliseconds = +value; }
34968 regex: '[0-5][0-9]',
34969 apply: function(value) { this.seconds = +value; }
34973 regex: '[0-9]|[1-5][0-9]',
34974 apply: function(value) { this.seconds = +value; }
34978 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
34979 apply: function(value) {
34980 if (this.hours === 12) {
34984 if (value === 'PM') {
34991 regex: '[+-]\\d{4}',
34992 apply: function(value) {
34993 var matches = value.match(/([+-])(\d{2})(\d{2})/),
34995 hours = matches[2],
34996 minutes = matches[3];
34997 this.hours += toInt(sign + hours);
34998 this.minutes += toInt(sign + minutes);
35003 regex: '[0-4][0-9]|5[0-3]'
35007 regex: '[0-9]|[1-4][0-9]|5[0-3]'
35011 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s')
35015 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35019 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35023 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35030 function createParser(format) {
35031 var map = [], regex = format.split('');
35033 // check for literal values
35034 var quoteIndex = format.indexOf('\'');
35035 if (quoteIndex > -1) {
35036 var inLiteral = false;
35037 format = format.split('');
35038 for (var i = quoteIndex; i < format.length; i++) {
35040 if (format[i] === '\'') {
35041 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
35044 } else { // end of literal
35051 if (format[i] === '\'') { // start of literal
35059 format = format.join('');
35062 angular.forEach(formatCodeToRegex, function(data) {
35063 var index = format.indexOf(data.key);
35066 format = format.split('');
35068 regex[index] = '(' + data.regex + ')';
35069 format[index] = '$'; // Custom symbol to define consumed part of format
35070 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
35074 format = format.join('');
35079 matcher: data.regex
35085 regex: new RegExp('^' + regex.join('') + '$'),
35086 map: orderByFilter(map, 'index')
35090 this.parse = function(input, format, baseDate) {
35091 if (!angular.isString(input) || !format) {
35095 format = $locale.DATETIME_FORMATS[format] || format;
35096 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
35098 if ($locale.id !== localeId) {
35102 if (!this.parsers[format]) {
35103 this.parsers[format] = createParser(format);
35106 var parser = this.parsers[format],
35107 regex = parser.regex,
35109 results = input.match(regex),
35111 if (results && results.length) {
35113 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
35115 year: baseDate.getFullYear(),
35116 month: baseDate.getMonth(),
35117 date: baseDate.getDate(),
35118 hours: baseDate.getHours(),
35119 minutes: baseDate.getMinutes(),
35120 seconds: baseDate.getSeconds(),
35121 milliseconds: baseDate.getMilliseconds()
35125 $log.warn('dateparser:', 'baseDate is not a valid date');
35127 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
35130 for (var i = 1, n = results.length; i < n; i++) {
35131 var mapper = map[i - 1];
35132 if (mapper.matcher === 'Z') {
35136 if (mapper.apply) {
35137 mapper.apply.call(fields, results[i]);
35141 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
35142 Date.prototype.setFullYear;
35143 var timesetter = tzOffset ? Date.prototype.setUTCHours :
35144 Date.prototype.setHours;
35146 if (isValid(fields.year, fields.month, fields.date)) {
35147 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
35148 dt = new Date(baseDate);
35149 datesetter.call(dt, fields.year, fields.month, fields.date);
35150 timesetter.call(dt, fields.hours, fields.minutes,
35151 fields.seconds, fields.milliseconds);
35154 datesetter.call(dt, fields.year, fields.month, fields.date);
35155 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
35156 fields.seconds || 0, fields.milliseconds || 0);
35164 // Check if date is valid for specific month (and year for February).
35165 // Month: 0 = Jan, 1 = Feb, etc
35166 function isValid(year, month, date) {
35171 if (month === 1 && date > 28) {
35172 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
35175 if (month === 3 || month === 5 || month === 8 || month === 10) {
35182 function toInt(str) {
35183 return parseInt(str, 10);
35186 this.toTimezone = toTimezone;
35187 this.fromTimezone = fromTimezone;
35188 this.timezoneToOffset = timezoneToOffset;
35189 this.addDateMinutes = addDateMinutes;
35190 this.convertTimezoneToLocal = convertTimezoneToLocal;
35192 function toTimezone(date, timezone) {
35193 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
35196 function fromTimezone(date, timezone) {
35197 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
35200 //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
35201 function timezoneToOffset(timezone, fallback) {
35202 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
35203 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
35206 function addDateMinutes(date, minutes) {
35207 date = new Date(date.getTime());
35208 date.setMinutes(date.getMinutes() + minutes);
35212 function convertTimezoneToLocal(date, timezone, reverse) {
35213 reverse = reverse ? -1 : 1;
35214 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
35215 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
35219 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
35220 // at most one element.
35221 angular.module('ui.bootstrap.isClass', [])
35222 .directive('uibIsClass', [
35224 function ($animate) {
35225 // 11111111 22222222
35226 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
35227 // 11111111 22222222
35228 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
35230 var dataPerTracked = {};
35234 compile: function (tElement, tAttrs) {
35235 var linkedScopes = [];
35236 var instances = [];
35237 var expToData = {};
35238 var lastActivated = null;
35239 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
35240 var onExp = onExpMatches[2];
35241 var expsStr = onExpMatches[1];
35242 var exps = expsStr.split(',');
35246 function linkFn(scope, element, attrs) {
35247 linkedScopes.push(scope);
35253 exps.forEach(function (exp, k) {
35254 addForExp(exp, scope);
35257 scope.$on('$destroy', removeScope);
35260 function addForExp(exp, scope) {
35261 var matches = exp.match(IS_REGEXP);
35262 var clazz = scope.$eval(matches[1]);
35263 var compareWithExp = matches[2];
35264 var data = expToData[exp];
35266 var watchFn = function (compareWithVal) {
35267 var newActivated = null;
35268 instances.some(function (instance) {
35269 var thisVal = instance.scope.$eval(onExp);
35270 if (thisVal === compareWithVal) {
35271 newActivated = instance;
35275 if (data.lastActivated !== newActivated) {
35276 if (data.lastActivated) {
35277 $animate.removeClass(data.lastActivated.element, clazz);
35279 if (newActivated) {
35280 $animate.addClass(newActivated.element, clazz);
35282 data.lastActivated = newActivated;
35285 expToData[exp] = data = {
35286 lastActivated: null,
35289 compareWithExp: compareWithExp,
35290 watcher: scope.$watch(compareWithExp, watchFn)
35293 data.watchFn(scope.$eval(compareWithExp));
35296 function removeScope(e) {
35297 var removedScope = e.targetScope;
35298 var index = linkedScopes.indexOf(removedScope);
35299 linkedScopes.splice(index, 1);
35300 instances.splice(index, 1);
35301 if (linkedScopes.length) {
35302 var newWatchScope = linkedScopes[0];
35303 angular.forEach(expToData, function (data) {
35304 if (data.scope === removedScope) {
35305 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
35306 data.scope = newWatchScope;
35317 angular.module('ui.bootstrap.position', [])
35320 * A set of utility methods for working with the DOM.
35321 * It is meant to be used where we need to absolute-position elements in
35322 * relation to another element (this is the case for tooltips, popovers,
35323 * typeahead suggestions etc.).
35325 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
35327 * Used by scrollbarWidth() function to cache scrollbar's width.
35328 * Do not access this variable directly, use scrollbarWidth() instead.
35330 var SCROLLBAR_WIDTH;
35331 var OVERFLOW_REGEX = {
35332 normal: /(auto|scroll)/,
35333 hidden: /(auto|scroll|hidden)/
35335 var PLACEMENT_REGEX = {
35336 auto: /\s?auto?\s?/i,
35337 primary: /^(top|bottom|left|right)$/,
35338 secondary: /^(top|bottom|left|right|center)$/,
35339 vertical: /^(top|bottom)$/
35345 * Provides a raw DOM element from a jQuery/jQLite element.
35347 * @param {element} elem - The element to convert.
35349 * @returns {element} A HTML element.
35351 getRawNode: function(elem) {
35352 return elem[0] || elem;
35356 * Provides a parsed number for a style property. Strips
35357 * units and casts invalid numbers to 0.
35359 * @param {string} value - The style value to parse.
35361 * @returns {number} A valid number.
35363 parseStyle: function(value) {
35364 value = parseFloat(value);
35365 return isFinite(value) ? value : 0;
35369 * Provides the closest positioned ancestor.
35371 * @param {element} element - The element to get the offest parent for.
35373 * @returns {element} The closest positioned ancestor.
35375 offsetParent: function(elem) {
35376 elem = this.getRawNode(elem);
35378 var offsetParent = elem.offsetParent || $document[0].documentElement;
35380 function isStaticPositioned(el) {
35381 return ($window.getComputedStyle(el).position || 'static') === 'static';
35384 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
35385 offsetParent = offsetParent.offsetParent;
35388 return offsetParent || $document[0].documentElement;
35392 * Provides the scrollbar width, concept from TWBS measureScrollbar()
35393 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
35395 * @returns {number} The width of the browser scollbar.
35397 scrollbarWidth: function() {
35398 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
35399 var scrollElem = angular.element('<div style="position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;"></div>');
35400 $document.find('body').append(scrollElem);
35401 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
35402 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
35403 scrollElem.remove();
35406 return SCROLLBAR_WIDTH;
35410 * Provides the closest scrollable ancestor.
35411 * A port of the jQuery UI scrollParent method:
35412 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
35414 * @param {element} elem - The element to find the scroll parent of.
35415 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
35416 * default is false.
35418 * @returns {element} A HTML element.
35420 scrollParent: function(elem, includeHidden) {
35421 elem = this.getRawNode(elem);
35423 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
35424 var documentEl = $document[0].documentElement;
35425 var elemStyle = $window.getComputedStyle(elem);
35426 var excludeStatic = elemStyle.position === 'absolute';
35427 var scrollParent = elem.parentElement || documentEl;
35429 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
35433 while (scrollParent.parentElement && scrollParent !== documentEl) {
35434 var spStyle = $window.getComputedStyle(scrollParent);
35435 if (excludeStatic && spStyle.position !== 'static') {
35436 excludeStatic = false;
35439 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
35442 scrollParent = scrollParent.parentElement;
35445 return scrollParent;
35449 * Provides read-only equivalent of jQuery's position function:
35450 * http://api.jquery.com/position/ - distance to closest positioned
35451 * ancestor. Does not account for margins by default like jQuery position.
35453 * @param {element} elem - The element to caclulate the position on.
35454 * @param {boolean=} [includeMargins=false] - Should margins be accounted
35455 * for, default is false.
35457 * @returns {object} An object with the following properties:
35459 * <li>**width**: the width of the element</li>
35460 * <li>**height**: the height of the element</li>
35461 * <li>**top**: distance to top edge of offset parent</li>
35462 * <li>**left**: distance to left edge of offset parent</li>
35465 position: function(elem, includeMagins) {
35466 elem = this.getRawNode(elem);
35468 var elemOffset = this.offset(elem);
35469 if (includeMagins) {
35470 var elemStyle = $window.getComputedStyle(elem);
35471 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
35472 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
35474 var parent = this.offsetParent(elem);
35475 var parentOffset = {top: 0, left: 0};
35477 if (parent !== $document[0].documentElement) {
35478 parentOffset = this.offset(parent);
35479 parentOffset.top += parent.clientTop - parent.scrollTop;
35480 parentOffset.left += parent.clientLeft - parent.scrollLeft;
35484 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
35485 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
35486 top: Math.round(elemOffset.top - parentOffset.top),
35487 left: Math.round(elemOffset.left - parentOffset.left)
35492 * Provides read-only equivalent of jQuery's offset function:
35493 * http://api.jquery.com/offset/ - distance to viewport. Does
35494 * not account for borders, margins, or padding on the body
35497 * @param {element} elem - The element to calculate the offset on.
35499 * @returns {object} An object with the following properties:
35501 * <li>**width**: the width of the element</li>
35502 * <li>**height**: the height of the element</li>
35503 * <li>**top**: distance to top edge of viewport</li>
35504 * <li>**right**: distance to bottom edge of viewport</li>
35507 offset: function(elem) {
35508 elem = this.getRawNode(elem);
35510 var elemBCR = elem.getBoundingClientRect();
35512 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
35513 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
35514 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
35515 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
35520 * Provides offset distance to the closest scrollable ancestor
35521 * or viewport. Accounts for border and scrollbar width.
35523 * Right and bottom dimensions represent the distance to the
35524 * respective edge of the viewport element. If the element
35525 * edge extends beyond the viewport, a negative value will be
35528 * @param {element} elem - The element to get the viewport offset for.
35529 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
35530 * of the first scrollable element, default is false.
35531 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
35532 * be accounted for, default is true.
35534 * @returns {object} An object with the following properties:
35536 * <li>**top**: distance to the top content edge of viewport element</li>
35537 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
35538 * <li>**left**: distance to the left content edge of viewport element</li>
35539 * <li>**right**: distance to the right content edge of viewport element</li>
35542 viewportOffset: function(elem, useDocument, includePadding) {
35543 elem = this.getRawNode(elem);
35544 includePadding = includePadding !== false ? true : false;
35546 var elemBCR = elem.getBoundingClientRect();
35547 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
35549 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
35550 var offsetParentBCR = offsetParent.getBoundingClientRect();
35552 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
35553 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
35554 if (offsetParent === $document[0].documentElement) {
35555 offsetBCR.top += $window.pageYOffset;
35556 offsetBCR.left += $window.pageXOffset;
35558 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
35559 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
35561 if (includePadding) {
35562 var offsetParentStyle = $window.getComputedStyle(offsetParent);
35563 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
35564 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
35565 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
35566 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
35570 top: Math.round(elemBCR.top - offsetBCR.top),
35571 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
35572 left: Math.round(elemBCR.left - offsetBCR.left),
35573 right: Math.round(offsetBCR.right - elemBCR.right)
35578 * Provides an array of placement values parsed from a placement string.
35579 * Along with the 'auto' indicator, supported placement strings are:
35581 * <li>top: element on top, horizontally centered on host element.</li>
35582 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
35583 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
35584 * <li>bottom: element on bottom, horizontally centered on host element.</li>
35585 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
35586 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
35587 * <li>left: element on left, vertically centered on host element.</li>
35588 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
35589 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
35590 * <li>right: element on right, vertically centered on host element.</li>
35591 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
35592 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
35594 * A placement string with an 'auto' indicator is expected to be
35595 * space separated from the placement, i.e: 'auto bottom-left' If
35596 * the primary and secondary placement values do not match 'top,
35597 * bottom, left, right' then 'top' will be the primary placement and
35598 * 'center' will be the secondary placement. If 'auto' is passed, true
35599 * will be returned as the 3rd value of the array.
35601 * @param {string} placement - The placement string to parse.
35603 * @returns {array} An array with the following values
35605 * <li>**[0]**: The primary placement.</li>
35606 * <li>**[1]**: The secondary placement.</li>
35607 * <li>**[2]**: If auto is passed: true, else undefined.</li>
35610 parsePlacement: function(placement) {
35611 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
35613 placement = placement.replace(PLACEMENT_REGEX.auto, '');
35616 placement = placement.split('-');
35618 placement[0] = placement[0] || 'top';
35619 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
35620 placement[0] = 'top';
35623 placement[1] = placement[1] || 'center';
35624 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
35625 placement[1] = 'center';
35629 placement[2] = true;
35631 placement[2] = false;
35638 * Provides coordinates for an element to be positioned relative to
35639 * another element. Passing 'auto' as part of the placement parameter
35640 * will enable smart placement - where the element fits. i.e:
35641 * 'auto left-top' will check to see if there is enough space to the left
35642 * of the hostElem to fit the targetElem, if not place right (same for secondary
35643 * top placement). Available space is calculated using the viewportOffset
35646 * @param {element} hostElem - The element to position against.
35647 * @param {element} targetElem - The element to position.
35648 * @param {string=} [placement=top] - The placement for the targetElem,
35649 * default is 'top'. 'center' is assumed as secondary placement for
35650 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
35653 * <li>top-right</li>
35654 * <li>top-left</li>
35656 * <li>bottom-left</li>
35657 * <li>bottom-right</li>
35659 * <li>left-top</li>
35660 * <li>left-bottom</li>
35662 * <li>right-top</li>
35663 * <li>right-bottom</li>
35665 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
35666 * be calculated from the body element, default is false.
35668 * @returns {object} An object with the following properties:
35670 * <li>**top**: Value for targetElem top.</li>
35671 * <li>**left**: Value for targetElem left.</li>
35672 * <li>**placement**: The resolved placement.</li>
35675 positionElements: function(hostElem, targetElem, placement, appendToBody) {
35676 hostElem = this.getRawNode(hostElem);
35677 targetElem = this.getRawNode(targetElem);
35679 // need to read from prop to support tests.
35680 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
35681 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
35683 placement = this.parsePlacement(placement);
35685 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
35686 var targetElemPos = {top: 0, left: 0, placement: ''};
35688 if (placement[2]) {
35689 var viewportOffset = this.viewportOffset(hostElem);
35691 var targetElemStyle = $window.getComputedStyle(targetElem);
35692 var adjustedSize = {
35693 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
35694 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
35697 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
35698 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
35699 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
35700 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
35703 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
35704 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
35705 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
35706 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
35709 if (placement[1] === 'center') {
35710 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35711 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
35712 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
35713 placement[1] = 'left';
35714 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
35715 placement[1] = 'right';
35718 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
35719 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
35720 placement[1] = 'top';
35721 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
35722 placement[1] = 'bottom';
35728 switch (placement[0]) {
35730 targetElemPos.top = hostElemPos.top - targetHeight;
35733 targetElemPos.top = hostElemPos.top + hostElemPos.height;
35736 targetElemPos.left = hostElemPos.left - targetWidth;
35739 targetElemPos.left = hostElemPos.left + hostElemPos.width;
35743 switch (placement[1]) {
35745 targetElemPos.top = hostElemPos.top;
35748 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
35751 targetElemPos.left = hostElemPos.left;
35754 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
35757 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35758 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
35760 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
35765 targetElemPos.top = Math.round(targetElemPos.top);
35766 targetElemPos.left = Math.round(targetElemPos.left);
35767 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
35769 return targetElemPos;
35773 * Provides a way for positioning tooltip & dropdown
35774 * arrows when using placement options beyond the standard
35775 * left, right, top, or bottom.
35777 * @param {element} elem - The tooltip/dropdown element.
35778 * @param {string} placement - The placement for the elem.
35780 positionArrow: function(elem, placement) {
35781 elem = this.getRawNode(elem);
35783 var isTooltip = true;
35785 var innerElem = elem.querySelector('.tooltip-inner');
35788 innerElem = elem.querySelector('.popover-inner');
35794 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
35799 placement = this.parsePlacement(placement);
35800 if (placement[1] === 'center') {
35801 // no adjustment necessary - just reset styles
35802 angular.element(arrowElem).css({top: '', bottom: '', right: '', left: '', margin: ''});
35806 var borderProp = 'border-' + placement[0] + '-width';
35807 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
35809 var borderRadiusProp = 'border-';
35810 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35811 borderRadiusProp += placement[0] + '-' + placement[1];
35813 borderRadiusProp += placement[1] + '-' + placement[0];
35815 borderRadiusProp += '-radius';
35816 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
35826 switch (placement[0]) {
35828 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
35831 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
35834 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
35837 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
35841 arrowCss[placement[1]] = borderRadius;
35843 angular.element(arrowElem).css(arrowCss);
35848 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position'])
35850 .value('$datepickerSuppressError', false)
35852 .constant('uibDatepickerConfig', {
35854 formatMonth: 'MMMM',
35855 formatYear: 'yyyy',
35856 formatDayHeader: 'EEE',
35857 formatDayTitle: 'MMMM yyyy',
35858 formatMonthTitle: 'yyyy',
35859 datepickerMode: 'day',
35868 shortcutPropagation: false,
35872 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser',
35873 function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
35875 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
35876 ngModelOptions = {};
35879 this.modes = ['day', 'month', 'year'];
35881 // Interpolated configuration attributes
35882 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) {
35883 self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];
35886 // Evaled configuration attributes
35887 angular.forEach(['showWeeks', 'startingDay', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) {
35888 self[key] = angular.isDefined($attrs[key]) ? $scope.$parent.$eval($attrs[key]) : datepickerConfig[key];
35891 // Watchable date attributes
35892 angular.forEach(['minDate', 'maxDate'], function(key) {
35894 $scope.$parent.$watch($attrs[key], function(value) {
35895 self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null;
35896 self.refreshView();
35899 self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
35903 angular.forEach(['minMode', 'maxMode'], function(key) {
35905 $scope.$parent.$watch($attrs[key], function(value) {
35906 self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
35907 if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
35908 key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
35909 $scope.datepickerMode = self[key];
35913 self[key] = $scope[key] = datepickerConfig[key] || null;
35917 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
35918 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
35920 if (angular.isDefined($attrs.initDate)) {
35921 this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
35922 $scope.$parent.$watch($attrs.initDate, function(initDate) {
35923 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
35924 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
35925 self.refreshView();
35929 this.activeDate = new Date();
35932 $scope.disabled = angular.isDefined($attrs.disabled) || false;
35933 if (angular.isDefined($attrs.ngDisabled)) {
35934 $scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
35935 $scope.disabled = disabled;
35936 self.refreshView();
35940 $scope.isActive = function(dateObject) {
35941 if (self.compare(dateObject.date, self.activeDate) === 0) {
35942 $scope.activeDateId = dateObject.uid;
35948 this.init = function(ngModelCtrl_) {
35949 ngModelCtrl = ngModelCtrl_;
35950 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
35952 if (ngModelCtrl.$modelValue) {
35953 this.activeDate = ngModelCtrl.$modelValue;
35956 ngModelCtrl.$render = function() {
35961 this.render = function() {
35962 if (ngModelCtrl.$viewValue) {
35963 var date = new Date(ngModelCtrl.$viewValue),
35964 isValid = !isNaN(date);
35967 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
35968 } else if (!$datepickerSuppressError) {
35969 $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
35972 this.refreshView();
35975 this.refreshView = function() {
35976 if (this.element) {
35977 $scope.selectedDt = null;
35978 this._refreshView();
35979 if ($scope.activeDt) {
35980 $scope.activeDateId = $scope.activeDt.uid;
35983 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35984 date = dateParser.fromTimezone(date, ngModelOptions.timezone);
35985 ngModelCtrl.$setValidity('dateDisabled', !date ||
35986 this.element && !this.isDisabled(date));
35990 this.createDateObject = function(date, format) {
35991 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35992 model = dateParser.fromTimezone(model, ngModelOptions.timezone);
35995 label: dateFilter(date, format),
35996 selected: model && this.compare(date, model) === 0,
35997 disabled: this.isDisabled(date),
35998 current: this.compare(date, new Date()) === 0,
35999 customClass: this.customClass(date) || null
36002 if (model && this.compare(date, model) === 0) {
36003 $scope.selectedDt = dt;
36006 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
36007 $scope.activeDt = dt;
36013 this.isDisabled = function(date) {
36014 return $scope.disabled ||
36015 this.minDate && this.compare(date, this.minDate) < 0 ||
36016 this.maxDate && this.compare(date, this.maxDate) > 0 ||
36017 $attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
36020 this.customClass = function(date) {
36021 return $scope.customClass({date: date, mode: $scope.datepickerMode});
36024 // Split array into smaller arrays
36025 this.split = function(arr, size) {
36027 while (arr.length > 0) {
36028 arrays.push(arr.splice(0, size));
36033 $scope.select = function(date) {
36034 if ($scope.datepickerMode === self.minMode) {
36035 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
36036 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
36037 dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
36038 ngModelCtrl.$setViewValue(dt);
36039 ngModelCtrl.$render();
36041 self.activeDate = date;
36042 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
36046 $scope.move = function(direction) {
36047 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
36048 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
36049 self.activeDate.setFullYear(year, month, 1);
36050 self.refreshView();
36053 $scope.toggleMode = function(direction) {
36054 direction = direction || 1;
36056 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
36057 $scope.datepickerMode === self.minMode && direction === -1) {
36061 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
36064 // Key event mapper
36065 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
36067 var focusElement = function() {
36068 self.element[0].focus();
36071 // Listen for focus requests from popup directive
36072 $scope.$on('uib:datepicker.focus', focusElement);
36074 $scope.keydown = function(evt) {
36075 var key = $scope.keys[evt.which];
36077 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
36081 evt.preventDefault();
36082 if (!self.shortcutPropagation) {
36083 evt.stopPropagation();
36086 if (key === 'enter' || key === 'space') {
36087 if (self.isDisabled(self.activeDate)) {
36088 return; // do nothing
36090 $scope.select(self.activeDate);
36091 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
36092 $scope.toggleMode(key === 'up' ? 1 : -1);
36094 self.handleKeyDown(key, evt);
36095 self.refreshView();
36100 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36101 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
36103 this.step = { months: 1 };
36104 this.element = $element;
36105 function getDaysInMonth(year, month) {
36106 return month === 1 && year % 4 === 0 &&
36107 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
36110 this.init = function(ctrl) {
36111 angular.extend(ctrl, this);
36112 scope.showWeeks = ctrl.showWeeks;
36113 ctrl.refreshView();
36116 this.getDates = function(startDate, n) {
36117 var dates = new Array(n), current = new Date(startDate), i = 0, date;
36119 date = new Date(current);
36121 current.setDate(current.getDate() + 1);
36126 this._refreshView = function() {
36127 var year = this.activeDate.getFullYear(),
36128 month = this.activeDate.getMonth(),
36129 firstDayOfMonth = new Date(this.activeDate);
36131 firstDayOfMonth.setFullYear(year, month, 1);
36133 var difference = this.startingDay - firstDayOfMonth.getDay(),
36134 numDisplayedFromPreviousMonth = difference > 0 ?
36135 7 - difference : - difference,
36136 firstDate = new Date(firstDayOfMonth);
36138 if (numDisplayedFromPreviousMonth > 0) {
36139 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
36142 // 42 is the number of days on a six-week calendar
36143 var days = this.getDates(firstDate, 42);
36144 for (var i = 0; i < 42; i ++) {
36145 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
36146 secondary: days[i].getMonth() !== month,
36147 uid: scope.uniqueId + '-' + i
36151 scope.labels = new Array(7);
36152 for (var j = 0; j < 7; j++) {
36153 scope.labels[j] = {
36154 abbr: dateFilter(days[j].date, this.formatDayHeader),
36155 full: dateFilter(days[j].date, 'EEEE')
36159 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
36160 scope.rows = this.split(days, 7);
36162 if (scope.showWeeks) {
36163 scope.weekNumbers = [];
36164 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
36165 numWeeks = scope.rows.length;
36166 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
36167 scope.weekNumbers.push(
36168 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
36173 this.compare = function(date1, date2) {
36174 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
36175 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36176 _date1.setFullYear(date1.getFullYear());
36177 _date2.setFullYear(date2.getFullYear());
36178 return _date1 - _date2;
36181 function getISO8601WeekNumber(date) {
36182 var checkDate = new Date(date);
36183 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
36184 var time = checkDate.getTime();
36185 checkDate.setMonth(0); // Compare with Jan 1
36186 checkDate.setDate(1);
36187 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
36190 this.handleKeyDown = function(key, evt) {
36191 var date = this.activeDate.getDate();
36193 if (key === 'left') {
36195 } else if (key === 'up') {
36197 } else if (key === 'right') {
36199 } else if (key === 'down') {
36201 } else if (key === 'pageup' || key === 'pagedown') {
36202 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
36203 this.activeDate.setMonth(month, 1);
36204 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
36205 } else if (key === 'home') {
36207 } else if (key === 'end') {
36208 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
36210 this.activeDate.setDate(date);
36214 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36215 this.step = { years: 1 };
36216 this.element = $element;
36218 this.init = function(ctrl) {
36219 angular.extend(ctrl, this);
36220 ctrl.refreshView();
36223 this._refreshView = function() {
36224 var months = new Array(12),
36225 year = this.activeDate.getFullYear(),
36228 for (var i = 0; i < 12; i++) {
36229 date = new Date(this.activeDate);
36230 date.setFullYear(year, i, 1);
36231 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
36232 uid: scope.uniqueId + '-' + i
36236 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
36237 scope.rows = this.split(months, 3);
36240 this.compare = function(date1, date2) {
36241 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
36242 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
36243 _date1.setFullYear(date1.getFullYear());
36244 _date2.setFullYear(date2.getFullYear());
36245 return _date1 - _date2;
36248 this.handleKeyDown = function(key, evt) {
36249 var date = this.activeDate.getMonth();
36251 if (key === 'left') {
36253 } else if (key === 'up') {
36255 } else if (key === 'right') {
36257 } else if (key === 'down') {
36259 } else if (key === 'pageup' || key === 'pagedown') {
36260 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
36261 this.activeDate.setFullYear(year);
36262 } else if (key === 'home') {
36264 } else if (key === 'end') {
36267 this.activeDate.setMonth(date);
36271 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36272 var columns, range;
36273 this.element = $element;
36275 function getStartingYear(year) {
36276 return parseInt((year - 1) / range, 10) * range + 1;
36279 this.yearpickerInit = function() {
36280 columns = this.yearColumns;
36281 range = this.yearRows * columns;
36282 this.step = { years: range };
36285 this._refreshView = function() {
36286 var years = new Array(range), date;
36288 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
36289 date = new Date(this.activeDate);
36290 date.setFullYear(start + i, 0, 1);
36291 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
36292 uid: scope.uniqueId + '-' + i
36296 scope.title = [years[0].label, years[range - 1].label].join(' - ');
36297 scope.rows = this.split(years, columns);
36298 scope.columns = columns;
36301 this.compare = function(date1, date2) {
36302 return date1.getFullYear() - date2.getFullYear();
36305 this.handleKeyDown = function(key, evt) {
36306 var date = this.activeDate.getFullYear();
36308 if (key === 'left') {
36310 } else if (key === 'up') {
36311 date = date - columns;
36312 } else if (key === 'right') {
36314 } else if (key === 'down') {
36315 date = date + columns;
36316 } else if (key === 'pageup' || key === 'pagedown') {
36317 date += (key === 'pageup' ? - 1 : 1) * range;
36318 } else if (key === 'home') {
36319 date = getStartingYear(this.activeDate.getFullYear());
36320 } else if (key === 'end') {
36321 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
36323 this.activeDate.setFullYear(date);
36327 .directive('uibDatepicker', function() {
36330 templateUrl: function(element, attrs) {
36331 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
36334 datepickerMode: '=?',
36337 shortcutPropagation: '&?'
36339 require: ['uibDatepicker', '^ngModel'],
36340 controller: 'UibDatepickerController',
36341 controllerAs: 'datepicker',
36342 link: function(scope, element, attrs, ctrls) {
36343 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
36345 datepickerCtrl.init(ngModelCtrl);
36350 .directive('uibDaypicker', function() {
36353 templateUrl: function(element, attrs) {
36354 return attrs.templateUrl || 'uib/template/datepicker/day.html';
36356 require: ['^uibDatepicker', 'uibDaypicker'],
36357 controller: 'UibDaypickerController',
36358 link: function(scope, element, attrs, ctrls) {
36359 var datepickerCtrl = ctrls[0],
36360 daypickerCtrl = ctrls[1];
36362 daypickerCtrl.init(datepickerCtrl);
36367 .directive('uibMonthpicker', function() {
36370 templateUrl: function(element, attrs) {
36371 return attrs.templateUrl || 'uib/template/datepicker/month.html';
36373 require: ['^uibDatepicker', 'uibMonthpicker'],
36374 controller: 'UibMonthpickerController',
36375 link: function(scope, element, attrs, ctrls) {
36376 var datepickerCtrl = ctrls[0],
36377 monthpickerCtrl = ctrls[1];
36379 monthpickerCtrl.init(datepickerCtrl);
36384 .directive('uibYearpicker', function() {
36387 templateUrl: function(element, attrs) {
36388 return attrs.templateUrl || 'uib/template/datepicker/year.html';
36390 require: ['^uibDatepicker', 'uibYearpicker'],
36391 controller: 'UibYearpickerController',
36392 link: function(scope, element, attrs, ctrls) {
36393 var ctrl = ctrls[0];
36394 angular.extend(ctrl, ctrls[1]);
36395 ctrl.yearpickerInit();
36397 ctrl.refreshView();
36402 .constant('uibDatepickerPopupConfig', {
36403 datepickerPopup: 'yyyy-MM-dd',
36404 datepickerPopupTemplateUrl: 'uib/template/datepicker/popup.html',
36405 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
36407 date: 'yyyy-MM-dd',
36408 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
36411 currentText: 'Today',
36412 clearText: 'Clear',
36414 closeOnDateSelection: true,
36415 appendToBody: false,
36416 showButtonBar: true,
36418 altInputFormats: []
36421 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
36422 function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
36425 isHtml5DateInput = false;
36426 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
36427 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
36428 ngModel, ngModelOptions, $popup, altInputFormats;
36430 scope.watchData = {};
36432 this.init = function(_ngModel_) {
36433 ngModel = _ngModel_;
36434 ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
36435 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
36436 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
36437 onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
36438 datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
36439 datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
36440 altInputFormats = angular.isDefined(attrs.altInputFormats) ? scope.$parent.$eval(attrs.altInputFormats) : datepickerPopupConfig.altInputFormats;
36442 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
36444 if (datepickerPopupConfig.html5Types[attrs.type]) {
36445 dateFormat = datepickerPopupConfig.html5Types[attrs.type];
36446 isHtml5DateInput = true;
36448 dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
36449 attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
36450 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
36451 // Invalidate the $modelValue to ensure that formatters re-run
36452 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
36453 if (newDateFormat !== dateFormat) {
36454 dateFormat = newDateFormat;
36455 ngModel.$modelValue = null;
36458 throw new Error('uibDatepickerPopup must have a date format specified.');
36465 throw new Error('uibDatepickerPopup must have a date format specified.');
36468 if (isHtml5DateInput && attrs.uibDatepickerPopup) {
36469 throw new Error('HTML5 date input types do not support custom formats.');
36472 // popup element used to display calendar
36473 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
36474 scope.ngModelOptions = angular.copy(ngModelOptions);
36475 scope.ngModelOptions.timezone = null;
36477 'ng-model': 'date',
36478 'ng-model-options': 'ngModelOptions',
36479 'ng-change': 'dateSelection(date)',
36480 'template-url': datepickerPopupTemplateUrl
36483 // datepicker element
36484 datepickerEl = angular.element(popupEl.children()[0]);
36485 datepickerEl.attr('template-url', datepickerTemplateUrl);
36487 if (isHtml5DateInput) {
36488 if (attrs.type === 'month') {
36489 datepickerEl.attr('datepicker-mode', '"month"');
36490 datepickerEl.attr('min-mode', 'month');
36494 if (attrs.datepickerOptions) {
36495 var options = scope.$parent.$eval(attrs.datepickerOptions);
36496 if (options && options.initDate) {
36497 scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone);
36498 datepickerEl.attr('init-date', 'initDate');
36499 delete options.initDate;
36501 angular.forEach(options, function(value, option) {
36502 datepickerEl.attr(cameltoDash(option), value);
36506 angular.forEach(['minMode', 'maxMode'], function(key) {
36508 scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
36509 scope.watchData[key] = value;
36511 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36515 angular.forEach(['datepickerMode', 'shortcutPropagation'], function(key) {
36517 var getAttribute = $parse(attrs[key]);
36520 return getAttribute(scope.$parent);
36524 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36526 // Propagate changes from datepicker to outside
36527 if (key === 'datepickerMode') {
36528 var setAttribute = getAttribute.assign;
36529 propConfig.set = function(v) {
36530 setAttribute(scope.$parent, v);
36534 Object.defineProperty(scope.watchData, key, propConfig);
36538 angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) {
36540 var getAttribute = $parse(attrs[key]);
36542 scope.$parent.$watch(getAttribute, function(value) {
36543 if (key === 'minDate' || key === 'maxDate') {
36544 cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium'));
36547 scope.watchData[key] = cache[key] || dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
36550 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36554 if (attrs.dateDisabled) {
36555 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
36558 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) {
36559 if (angular.isDefined(attrs[key])) {
36560 datepickerEl.attr(cameltoDash(key), attrs[key]);
36564 if (attrs.customClass) {
36565 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
36568 if (!isHtml5DateInput) {
36569 // Internal API to maintain the correct ng-invalid-[key] class
36570 ngModel.$$parserName = 'date';
36571 ngModel.$validators.date = validator;
36572 ngModel.$parsers.unshift(parseDate);
36573 ngModel.$formatters.push(function(value) {
36574 if (ngModel.$isEmpty(value)) {
36575 scope.date = value;
36578 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36579 return dateFilter(scope.date, dateFormat);
36582 ngModel.$formatters.push(function(value) {
36583 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36588 // Detect changes in the view from the text box
36589 ngModel.$viewChangeListeners.push(function() {
36590 scope.date = parseDateString(ngModel.$viewValue);
36593 element.bind('keydown', inputKeydownBind);
36595 $popup = $compile(popupEl)(scope);
36596 // Prevent jQuery cache memory leak (template is now redundant after linking)
36599 if (appendToBody) {
36600 $document.find('body').append($popup);
36602 element.after($popup);
36605 scope.$on('$destroy', function() {
36606 if (scope.isOpen === true) {
36607 if (!$rootScope.$$phase) {
36608 scope.$apply(function() {
36609 scope.isOpen = false;
36615 element.unbind('keydown', inputKeydownBind);
36616 $document.unbind('click', documentClickBind);
36620 scope.getText = function(key) {
36621 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
36624 scope.isDisabled = function(date) {
36625 if (date === 'today') {
36629 return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 ||
36630 scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
36633 scope.compare = function(date1, date2) {
36634 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36638 scope.dateSelection = function(dt) {
36639 if (angular.isDefined(dt)) {
36642 var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
36644 ngModel.$setViewValue(date);
36646 if (closeOnDateSelection) {
36647 scope.isOpen = false;
36648 element[0].focus();
36652 scope.keydown = function(evt) {
36653 if (evt.which === 27) {
36654 evt.stopPropagation();
36655 scope.isOpen = false;
36656 element[0].focus();
36660 scope.select = function(date) {
36661 if (date === 'today') {
36662 var today = new Date();
36663 if (angular.isDate(scope.date)) {
36664 date = new Date(scope.date);
36665 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
36667 date = new Date(today.setHours(0, 0, 0, 0));
36670 scope.dateSelection(date);
36673 scope.close = function() {
36674 scope.isOpen = false;
36675 element[0].focus();
36678 scope.disabled = angular.isDefined(attrs.disabled) || false;
36679 if (attrs.ngDisabled) {
36680 scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
36681 scope.disabled = disabled;
36685 scope.$watch('isOpen', function(value) {
36687 if (!scope.disabled) {
36688 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
36689 scope.position.top = scope.position.top + element.prop('offsetHeight');
36691 $timeout(function() {
36693 scope.$broadcast('uib:datepicker.focus');
36695 $document.bind('click', documentClickBind);
36698 scope.isOpen = false;
36701 $document.unbind('click', documentClickBind);
36705 function cameltoDash(string) {
36706 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
36709 function parseDateString(viewValue) {
36710 var date = dateParser.parse(viewValue, dateFormat, scope.date);
36712 for (var i = 0; i < altInputFormats.length; i++) {
36713 date = dateParser.parse(viewValue, altInputFormats[i], scope.date);
36714 if (!isNaN(date)) {
36722 function parseDate(viewValue) {
36723 if (angular.isNumber(viewValue)) {
36724 // presumably timestamp to date object
36725 viewValue = new Date(viewValue);
36732 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
36736 if (angular.isString(viewValue)) {
36737 var date = parseDateString(viewValue);
36738 if (!isNaN(date)) {
36739 return dateParser.toTimezone(date, ngModelOptions.timezone);
36743 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
36746 function validator(modelValue, viewValue) {
36747 var value = modelValue || viewValue;
36749 if (!attrs.ngRequired && !value) {
36753 if (angular.isNumber(value)) {
36754 value = new Date(value);
36761 if (angular.isDate(value) && !isNaN(value)) {
36765 if (angular.isString(value)) {
36766 return !isNaN(parseDateString(viewValue));
36772 function documentClickBind(event) {
36773 if (!scope.isOpen && scope.disabled) {
36777 var popup = $popup[0];
36778 var dpContainsTarget = element[0].contains(event.target);
36779 // The popup node may not be an element node
36780 // In some browsers (IE) only element nodes have the 'contains' function
36781 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
36782 if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
36783 scope.$apply(function() {
36784 scope.isOpen = false;
36789 function inputKeydownBind(evt) {
36790 if (evt.which === 27 && scope.isOpen) {
36791 evt.preventDefault();
36792 evt.stopPropagation();
36793 scope.$apply(function() {
36794 scope.isOpen = false;
36796 element[0].focus();
36797 } else if (evt.which === 40 && !scope.isOpen) {
36798 evt.preventDefault();
36799 evt.stopPropagation();
36800 scope.$apply(function() {
36801 scope.isOpen = true;
36807 .directive('uibDatepickerPopup', function() {
36809 require: ['ngModel', 'uibDatepickerPopup'],
36810 controller: 'UibDatepickerPopupController',
36819 link: function(scope, element, attrs, ctrls) {
36820 var ngModel = ctrls[0],
36823 ctrl.init(ngModel);
36828 .directive('uibDatepickerPopupWrap', function() {
36832 templateUrl: function(element, attrs) {
36833 return attrs.templateUrl || 'uib/template/datepicker/popup.html';
36838 angular.module('ui.bootstrap.debounce', [])
36840 * A helper, internal service that debounces a function
36842 .factory('$$debounce', ['$timeout', function($timeout) {
36843 return function(callback, debounceTime) {
36844 var timeoutPromise;
36846 return function() {
36848 var args = Array.prototype.slice.call(arguments);
36849 if (timeoutPromise) {
36850 $timeout.cancel(timeoutPromise);
36853 timeoutPromise = $timeout(function() {
36854 callback.apply(self, args);
36860 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
36862 .constant('uibDropdownConfig', {
36863 appendToOpenClass: 'uib-dropdown-open',
36867 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
36868 var openScope = null;
36870 this.open = function(dropdownScope) {
36872 $document.on('click', closeDropdown);
36873 $document.on('keydown', keybindFilter);
36876 if (openScope && openScope !== dropdownScope) {
36877 openScope.isOpen = false;
36880 openScope = dropdownScope;
36883 this.close = function(dropdownScope) {
36884 if (openScope === dropdownScope) {
36886 $document.off('click', closeDropdown);
36887 $document.off('keydown', keybindFilter);
36891 var closeDropdown = function(evt) {
36892 // This method may still be called during the same mouse event that
36893 // unbound this event handler. So check openScope before proceeding.
36894 if (!openScope) { return; }
36896 if (evt && openScope.getAutoClose() === 'disabled') { return; }
36898 if (evt && evt.which === 3) { return; }
36900 var toggleElement = openScope.getToggleElement();
36901 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
36905 var dropdownElement = openScope.getDropdownElement();
36906 if (evt && openScope.getAutoClose() === 'outsideClick' &&
36907 dropdownElement && dropdownElement[0].contains(evt.target)) {
36911 openScope.isOpen = false;
36913 if (!$rootScope.$$phase) {
36914 openScope.$apply();
36918 var keybindFilter = function(evt) {
36919 if (evt.which === 27) {
36920 openScope.focusToggleElement();
36922 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
36923 evt.preventDefault();
36924 evt.stopPropagation();
36925 openScope.focusDropdownEntry(evt.which);
36930 .controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
36932 scope = $scope.$new(), // create a child scope so we are not polluting original one
36934 appendToOpenClass = dropdownConfig.appendToOpenClass,
36935 openClass = dropdownConfig.openClass,
36937 setIsOpen = angular.noop,
36938 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
36939 appendToBody = false,
36941 keynavEnabled = false,
36942 selectedOption = null,
36943 body = $document.find('body');
36945 $element.addClass('dropdown');
36947 this.init = function() {
36948 if ($attrs.isOpen) {
36949 getIsOpen = $parse($attrs.isOpen);
36950 setIsOpen = getIsOpen.assign;
36952 $scope.$watch(getIsOpen, function(value) {
36953 scope.isOpen = !!value;
36957 if (angular.isDefined($attrs.dropdownAppendTo)) {
36958 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
36960 appendTo = angular.element(appendToEl);
36964 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
36965 keynavEnabled = angular.isDefined($attrs.keyboardNav);
36967 if (appendToBody && !appendTo) {
36971 if (appendTo && self.dropdownMenu) {
36972 appendTo.append(self.dropdownMenu);
36973 $element.on('$destroy', function handleDestroyEvent() {
36974 self.dropdownMenu.remove();
36979 this.toggle = function(open) {
36980 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
36983 // Allow other directives to watch status
36984 this.isOpen = function() {
36985 return scope.isOpen;
36988 scope.getToggleElement = function() {
36989 return self.toggleElement;
36992 scope.getAutoClose = function() {
36993 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
36996 scope.getElement = function() {
37000 scope.isKeynavEnabled = function() {
37001 return keynavEnabled;
37004 scope.focusDropdownEntry = function(keyCode) {
37005 var elems = self.dropdownMenu ? //If append to body is used.
37006 angular.element(self.dropdownMenu).find('a') :
37007 $element.find('ul').eq(0).find('a');
37011 if (!angular.isNumber(self.selectedOption)) {
37012 self.selectedOption = 0;
37014 self.selectedOption = self.selectedOption === elems.length - 1 ?
37015 self.selectedOption :
37016 self.selectedOption + 1;
37021 if (!angular.isNumber(self.selectedOption)) {
37022 self.selectedOption = elems.length - 1;
37024 self.selectedOption = self.selectedOption === 0 ?
37025 0 : self.selectedOption - 1;
37030 elems[self.selectedOption].focus();
37033 scope.getDropdownElement = function() {
37034 return self.dropdownMenu;
37037 scope.focusToggleElement = function() {
37038 if (self.toggleElement) {
37039 self.toggleElement[0].focus();
37043 scope.$watch('isOpen', function(isOpen, wasOpen) {
37044 if (appendTo && self.dropdownMenu) {
37045 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
37050 top: pos.top + 'px',
37051 display: isOpen ? 'block' : 'none'
37054 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
37056 css.left = pos.left + 'px';
37057 css.right = 'auto';
37060 css.right = window.innerWidth -
37061 (pos.left + $element.prop('offsetWidth')) + 'px';
37064 // Need to adjust our positioning to be relative to the appendTo container
37065 // if it's not the body element
37066 if (!appendToBody) {
37067 var appendOffset = $position.offset(appendTo);
37069 css.top = pos.top - appendOffset.top + 'px';
37072 css.left = pos.left - appendOffset.left + 'px';
37074 css.right = window.innerWidth -
37075 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
37079 self.dropdownMenu.css(css);
37082 var openContainer = appendTo ? appendTo : $element;
37084 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
37085 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
37086 toggleInvoker($scope, { open: !!isOpen });
37091 if (self.dropdownMenuTemplateUrl) {
37092 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
37093 templateScope = scope.$new();
37094 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
37095 var newEl = dropdownElement;
37096 self.dropdownMenu.replaceWith(newEl);
37097 self.dropdownMenu = newEl;
37102 scope.focusToggleElement();
37103 uibDropdownService.open(scope);
37105 if (self.dropdownMenuTemplateUrl) {
37106 if (templateScope) {
37107 templateScope.$destroy();
37109 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
37110 self.dropdownMenu.replaceWith(newEl);
37111 self.dropdownMenu = newEl;
37114 uibDropdownService.close(scope);
37115 self.selectedOption = null;
37118 if (angular.isFunction(setIsOpen)) {
37119 setIsOpen($scope, isOpen);
37123 $scope.$on('$locationChangeSuccess', function() {
37124 if (scope.getAutoClose() !== 'disabled') {
37125 scope.isOpen = false;
37130 .directive('uibDropdown', function() {
37132 controller: 'UibDropdownController',
37133 link: function(scope, element, attrs, dropdownCtrl) {
37134 dropdownCtrl.init();
37139 .directive('uibDropdownMenu', function() {
37142 require: '?^uibDropdown',
37143 link: function(scope, element, attrs, dropdownCtrl) {
37144 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
37148 element.addClass('dropdown-menu');
37150 var tplUrl = attrs.templateUrl;
37152 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
37155 if (!dropdownCtrl.dropdownMenu) {
37156 dropdownCtrl.dropdownMenu = element;
37162 .directive('uibDropdownToggle', function() {
37164 require: '?^uibDropdown',
37165 link: function(scope, element, attrs, dropdownCtrl) {
37166 if (!dropdownCtrl) {
37170 element.addClass('dropdown-toggle');
37172 dropdownCtrl.toggleElement = element;
37174 var toggleDropdown = function(event) {
37175 event.preventDefault();
37177 if (!element.hasClass('disabled') && !attrs.disabled) {
37178 scope.$apply(function() {
37179 dropdownCtrl.toggle();
37184 element.bind('click', toggleDropdown);
37187 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
37188 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
37189 element.attr('aria-expanded', !!isOpen);
37192 scope.$on('$destroy', function() {
37193 element.unbind('click', toggleDropdown);
37199 angular.module('ui.bootstrap.stackedMap', [])
37201 * A helper, internal data structure that acts as a map but also allows getting / removing
37202 * elements in the LIFO order
37204 .factory('$$stackedMap', function() {
37206 createNew: function() {
37210 add: function(key, value) {
37216 get: function(key) {
37217 for (var i = 0; i < stack.length; i++) {
37218 if (key === stack[i].key) {
37225 for (var i = 0; i < stack.length; i++) {
37226 keys.push(stack[i].key);
37231 return stack[stack.length - 1];
37233 remove: function(key) {
37235 for (var i = 0; i < stack.length; i++) {
37236 if (key === stack[i].key) {
37241 return stack.splice(idx, 1)[0];
37243 removeTop: function() {
37244 return stack.splice(stack.length - 1, 1)[0];
37246 length: function() {
37247 return stack.length;
37253 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
37255 * A helper, internal data structure that stores all references attached to key
37257 .factory('$$multiMap', function() {
37259 createNew: function() {
37263 entries: function() {
37264 return Object.keys(map).map(function(key) {
37271 get: function(key) {
37274 hasKey: function(key) {
37278 return Object.keys(map);
37280 put: function(key, value) {
37285 map[key].push(value);
37287 remove: function(key, value) {
37288 var values = map[key];
37294 var idx = values.indexOf(value);
37297 values.splice(idx, 1);
37300 if (!values.length) {
37310 * Pluggable resolve mechanism for the modal resolve resolution
37311 * Supports UI Router's $resolve service
37313 .provider('$uibResolve', function() {
37314 var resolve = this;
37315 this.resolver = null;
37317 this.setResolver = function(resolver) {
37318 this.resolver = resolver;
37321 this.$get = ['$injector', '$q', function($injector, $q) {
37322 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
37324 resolve: function(invocables, locals, parent, self) {
37326 return resolver.resolve(invocables, locals, parent, self);
37331 angular.forEach(invocables, function(value) {
37332 if (angular.isFunction(value) || angular.isArray(value)) {
37333 promises.push($q.resolve($injector.invoke(value)));
37334 } else if (angular.isString(value)) {
37335 promises.push($q.resolve($injector.get(value)));
37337 promises.push($q.resolve(value));
37341 return $q.all(promises).then(function(resolves) {
37342 var resolveObj = {};
37343 var resolveIter = 0;
37344 angular.forEach(invocables, function(value, key) {
37345 resolveObj[key] = resolves[resolveIter++];
37356 * A helper directive for the $modal service. It creates a backdrop element.
37358 .directive('uibModalBackdrop', ['$animateCss', '$injector', '$uibModalStack',
37359 function($animateCss, $injector, $modalStack) {
37362 templateUrl: 'uib/template/modal/backdrop.html',
37363 compile: function(tElement, tAttrs) {
37364 tElement.addClass(tAttrs.backdropClass);
37369 function linkFn(scope, element, attrs) {
37370 if (attrs.modalInClass) {
37371 $animateCss(element, {
37372 addClass: attrs.modalInClass
37375 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37376 var done = setIsAsync();
37377 if (scope.modalOptions.animation) {
37378 $animateCss(element, {
37379 removeClass: attrs.modalInClass
37380 }).start().then(done);
37389 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animate', '$animateCss', '$document',
37390 function($modalStack, $q, $animate, $animateCss, $document) {
37397 templateUrl: function(tElement, tAttrs) {
37398 return tAttrs.templateUrl || 'uib/template/modal/window.html';
37400 link: function(scope, element, attrs) {
37401 element.addClass(attrs.windowClass || '');
37402 element.addClass(attrs.windowTopClass || '');
37403 scope.size = attrs.size;
37405 scope.close = function(evt) {
37406 var modal = $modalStack.getTop();
37407 if (modal && modal.value.backdrop &&
37408 modal.value.backdrop !== 'static' &&
37409 evt.target === evt.currentTarget) {
37410 evt.preventDefault();
37411 evt.stopPropagation();
37412 $modalStack.dismiss(modal.key, 'backdrop click');
37416 // moved from template to fix issue #2280
37417 element.on('click', scope.close);
37419 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
37420 // We can detect that by using this property in the template associated with this directive and then use
37421 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
37422 scope.$isRendered = true;
37424 // Deferred object that will be resolved when this modal is render.
37425 var modalRenderDeferObj = $q.defer();
37426 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
37427 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
37428 attrs.$observe('modalRender', function(value) {
37429 if (value === 'true') {
37430 modalRenderDeferObj.resolve();
37434 modalRenderDeferObj.promise.then(function() {
37435 var animationPromise = null;
37437 if (attrs.modalInClass) {
37438 animationPromise = $animateCss(element, {
37439 addClass: attrs.modalInClass
37442 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37443 var done = setIsAsync();
37445 $animateCss(element, {
37446 removeClass: attrs.modalInClass
37447 }).start().then(done);
37449 $animate.removeClass(element, attrs.modalInClass).then(done);
37455 $q.when(animationPromise).then(function() {
37457 * If something within the freshly-opened modal already has focus (perhaps via a
37458 * directive that causes focus). then no need to try and focus anything.
37460 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
37461 var inputWithAutofocus = element[0].querySelector('[autofocus]');
37463 * Auto-focusing of a freshly-opened modal element causes any child elements
37464 * with the autofocus attribute to lose focus. This is an issue on touch
37465 * based devices which will show and then hide the onscreen keyboard.
37466 * Attempts to refocus the autofocus element via JavaScript will not reopen
37467 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
37468 * the modal element if the modal does not contain an autofocus element.
37470 if (inputWithAutofocus) {
37471 inputWithAutofocus.focus();
37473 element[0].focus();
37478 // Notify {@link $modalStack} that modal is rendered.
37479 var modal = $modalStack.getTop();
37481 $modalStack.modalRendered(modal.key);
37488 .directive('uibModalAnimationClass', function() {
37490 compile: function(tElement, tAttrs) {
37491 if (tAttrs.modalAnimation) {
37492 tElement.addClass(tAttrs.uibModalAnimationClass);
37498 .directive('uibModalTransclude', function() {
37500 link: function(scope, element, attrs, controller, transclude) {
37501 transclude(scope.$parent, function(clone) {
37503 element.append(clone);
37509 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
37510 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
37511 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
37512 var OPENED_MODAL_CLASS = 'modal-open';
37514 var backdropDomEl, backdropScope;
37515 var openedWindows = $$stackedMap.createNew();
37516 var openedClasses = $$multiMap.createNew();
37517 var $modalStack = {
37518 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
37521 //Modal focus behavior
37522 var focusableElementList;
37523 var focusIndex = 0;
37524 var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
37525 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
37526 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
37528 function backdropIndex() {
37529 var topBackdropIndex = -1;
37530 var opened = openedWindows.keys();
37531 for (var i = 0; i < opened.length; i++) {
37532 if (openedWindows.get(opened[i]).value.backdrop) {
37533 topBackdropIndex = i;
37536 return topBackdropIndex;
37539 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
37540 if (backdropScope) {
37541 backdropScope.index = newBackdropIndex;
37545 function removeModalWindow(modalInstance, elementToReceiveFocus) {
37546 var modalWindow = openedWindows.get(modalInstance).value;
37547 var appendToElement = modalWindow.appendTo;
37549 //clean up the stack
37550 openedWindows.remove(modalInstance);
37552 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
37553 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
37554 openedClasses.remove(modalBodyClass, modalInstance);
37555 appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
37556 toggleTopWindowClass(true);
37558 checkRemoveBackdrop();
37560 //move focus to specified element if available, or else to body
37561 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
37562 elementToReceiveFocus.focus();
37563 } else if (appendToElement.focus) {
37564 appendToElement.focus();
37568 // Add or remove "windowTopClass" from the top window in the stack
37569 function toggleTopWindowClass(toggleSwitch) {
37572 if (openedWindows.length() > 0) {
37573 modalWindow = openedWindows.top().value;
37574 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
37578 function checkRemoveBackdrop() {
37579 //remove backdrop if no longer needed
37580 if (backdropDomEl && backdropIndex() === -1) {
37581 var backdropScopeRef = backdropScope;
37582 removeAfterAnimate(backdropDomEl, backdropScope, function() {
37583 backdropScopeRef = null;
37585 backdropDomEl = undefined;
37586 backdropScope = undefined;
37590 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
37592 var asyncPromise = null;
37593 var setIsAsync = function() {
37594 if (!asyncDeferred) {
37595 asyncDeferred = $q.defer();
37596 asyncPromise = asyncDeferred.promise;
37599 return function asyncDone() {
37600 asyncDeferred.resolve();
37603 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
37605 // Note that it's intentional that asyncPromise might be null.
37606 // That's when setIsAsync has not been called during the
37607 // NOW_CLOSING_EVENT broadcast.
37608 return $q.when(asyncPromise).then(afterAnimating);
37610 function afterAnimating() {
37611 if (afterAnimating.done) {
37614 afterAnimating.done = true;
37616 $animateCss(domEl, {
37618 }).start().then(function() {
37620 if (closedDeferred) {
37621 closedDeferred.resolve();
37632 $document.on('keydown', keydownListener);
37634 $rootScope.$on('$destroy', function() {
37635 $document.off('keydown', keydownListener);
37638 function keydownListener(evt) {
37639 if (evt.isDefaultPrevented()) {
37643 var modal = openedWindows.top();
37645 switch (evt.which) {
37647 if (modal.value.keyboard) {
37648 evt.preventDefault();
37649 $rootScope.$apply(function() {
37650 $modalStack.dismiss(modal.key, 'escape key press');
37656 $modalStack.loadFocusElementList(modal);
37657 var focusChanged = false;
37658 if (evt.shiftKey) {
37659 if ($modalStack.isFocusInFirstItem(evt)) {
37660 focusChanged = $modalStack.focusLastFocusableElement();
37663 if ($modalStack.isFocusInLastItem(evt)) {
37664 focusChanged = $modalStack.focusFirstFocusableElement();
37668 if (focusChanged) {
37669 evt.preventDefault();
37670 evt.stopPropagation();
37678 $modalStack.open = function(modalInstance, modal) {
37679 var modalOpener = $document[0].activeElement,
37680 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
37682 toggleTopWindowClass(false);
37684 openedWindows.add(modalInstance, {
37685 deferred: modal.deferred,
37686 renderDeferred: modal.renderDeferred,
37687 closedDeferred: modal.closedDeferred,
37688 modalScope: modal.scope,
37689 backdrop: modal.backdrop,
37690 keyboard: modal.keyboard,
37691 openedClass: modal.openedClass,
37692 windowTopClass: modal.windowTopClass,
37693 animation: modal.animation,
37694 appendTo: modal.appendTo
37697 openedClasses.put(modalBodyClass, modalInstance);
37699 var appendToElement = modal.appendTo,
37700 currBackdropIndex = backdropIndex();
37702 if (!appendToElement.length) {
37703 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
37706 if (currBackdropIndex >= 0 && !backdropDomEl) {
37707 backdropScope = $rootScope.$new(true);
37708 backdropScope.modalOptions = modal;
37709 backdropScope.index = currBackdropIndex;
37710 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
37711 backdropDomEl.attr('backdrop-class', modal.backdropClass);
37712 if (modal.animation) {
37713 backdropDomEl.attr('modal-animation', 'true');
37715 $compile(backdropDomEl)(backdropScope);
37716 $animate.enter(backdropDomEl, appendToElement);
37719 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
37720 angularDomEl.attr({
37721 'template-url': modal.windowTemplateUrl,
37722 'window-class': modal.windowClass,
37723 'window-top-class': modal.windowTopClass,
37724 'size': modal.size,
37725 'index': openedWindows.length() - 1,
37726 'animate': 'animate'
37727 }).html(modal.content);
37728 if (modal.animation) {
37729 angularDomEl.attr('modal-animation', 'true');
37732 $animate.enter(angularDomEl, appendToElement)
37734 $compile(angularDomEl)(modal.scope);
37735 $animate.addClass(appendToElement, modalBodyClass);
37738 openedWindows.top().value.modalDomEl = angularDomEl;
37739 openedWindows.top().value.modalOpener = modalOpener;
37741 $modalStack.clearFocusListCache();
37744 function broadcastClosing(modalWindow, resultOrReason, closing) {
37745 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
37748 $modalStack.close = function(modalInstance, result) {
37749 var modalWindow = openedWindows.get(modalInstance);
37750 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
37751 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37752 modalWindow.value.deferred.resolve(result);
37753 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37756 return !modalWindow;
37759 $modalStack.dismiss = function(modalInstance, reason) {
37760 var modalWindow = openedWindows.get(modalInstance);
37761 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
37762 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37763 modalWindow.value.deferred.reject(reason);
37764 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37767 return !modalWindow;
37770 $modalStack.dismissAll = function(reason) {
37771 var topModal = this.getTop();
37772 while (topModal && this.dismiss(topModal.key, reason)) {
37773 topModal = this.getTop();
37777 $modalStack.getTop = function() {
37778 return openedWindows.top();
37781 $modalStack.modalRendered = function(modalInstance) {
37782 var modalWindow = openedWindows.get(modalInstance);
37784 modalWindow.value.renderDeferred.resolve();
37788 $modalStack.focusFirstFocusableElement = function() {
37789 if (focusableElementList.length > 0) {
37790 focusableElementList[0].focus();
37795 $modalStack.focusLastFocusableElement = function() {
37796 if (focusableElementList.length > 0) {
37797 focusableElementList[focusableElementList.length - 1].focus();
37803 $modalStack.isFocusInFirstItem = function(evt) {
37804 if (focusableElementList.length > 0) {
37805 return (evt.target || evt.srcElement) === focusableElementList[0];
37810 $modalStack.isFocusInLastItem = function(evt) {
37811 if (focusableElementList.length > 0) {
37812 return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
37817 $modalStack.clearFocusListCache = function() {
37818 focusableElementList = [];
37822 $modalStack.loadFocusElementList = function(modalWindow) {
37823 if (focusableElementList === undefined || !focusableElementList.length) {
37825 var modalDomE1 = modalWindow.value.modalDomEl;
37826 if (modalDomE1 && modalDomE1.length) {
37827 focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
37833 return $modalStack;
37836 .provider('$uibModal', function() {
37837 var $modalProvider = {
37840 backdrop: true, //can also be false or 'static'
37843 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
37844 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
37847 function getTemplatePromise(options) {
37848 return options.template ? $q.when(options.template) :
37849 $templateRequest(angular.isFunction(options.templateUrl) ?
37850 options.templateUrl() : options.templateUrl);
37853 var promiseChain = null;
37854 $modal.getPromiseChain = function() {
37855 return promiseChain;
37858 $modal.open = function(modalOptions) {
37859 var modalResultDeferred = $q.defer();
37860 var modalOpenedDeferred = $q.defer();
37861 var modalClosedDeferred = $q.defer();
37862 var modalRenderDeferred = $q.defer();
37864 //prepare an instance of a modal to be injected into controllers and returned to a caller
37865 var modalInstance = {
37866 result: modalResultDeferred.promise,
37867 opened: modalOpenedDeferred.promise,
37868 closed: modalClosedDeferred.promise,
37869 rendered: modalRenderDeferred.promise,
37870 close: function (result) {
37871 return $modalStack.close(modalInstance, result);
37873 dismiss: function (reason) {
37874 return $modalStack.dismiss(modalInstance, reason);
37878 //merge and clean up options
37879 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
37880 modalOptions.resolve = modalOptions.resolve || {};
37881 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
37884 if (!modalOptions.template && !modalOptions.templateUrl) {
37885 throw new Error('One of template or templateUrl options is required.');
37888 var templateAndResolvePromise =
37889 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
37891 function resolveWithTemplate() {
37892 return templateAndResolvePromise;
37895 // Wait for the resolution of the existing promise chain.
37896 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
37897 // Then add to $modalStack and resolve opened.
37898 // Finally clean up the chain variable if no subsequent modal has overwritten it.
37900 samePromise = promiseChain = $q.all([promiseChain])
37901 .then(resolveWithTemplate, resolveWithTemplate)
37902 .then(function resolveSuccess(tplAndVars) {
37903 var providedScope = modalOptions.scope || $rootScope;
37905 var modalScope = providedScope.$new();
37906 modalScope.$close = modalInstance.close;
37907 modalScope.$dismiss = modalInstance.dismiss;
37909 modalScope.$on('$destroy', function() {
37910 if (!modalScope.$$uibDestructionScheduled) {
37911 modalScope.$dismiss('$uibUnscheduledDestruction');
37915 var ctrlInstance, ctrlLocals = {};
37918 if (modalOptions.controller) {
37919 ctrlLocals.$scope = modalScope;
37920 ctrlLocals.$uibModalInstance = modalInstance;
37921 angular.forEach(tplAndVars[1], function(value, key) {
37922 ctrlLocals[key] = value;
37925 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
37926 if (modalOptions.controllerAs) {
37927 if (modalOptions.bindToController) {
37928 ctrlInstance.$close = modalScope.$close;
37929 ctrlInstance.$dismiss = modalScope.$dismiss;
37930 angular.extend(ctrlInstance, providedScope);
37933 modalScope[modalOptions.controllerAs] = ctrlInstance;
37937 $modalStack.open(modalInstance, {
37939 deferred: modalResultDeferred,
37940 renderDeferred: modalRenderDeferred,
37941 closedDeferred: modalClosedDeferred,
37942 content: tplAndVars[0],
37943 animation: modalOptions.animation,
37944 backdrop: modalOptions.backdrop,
37945 keyboard: modalOptions.keyboard,
37946 backdropClass: modalOptions.backdropClass,
37947 windowTopClass: modalOptions.windowTopClass,
37948 windowClass: modalOptions.windowClass,
37949 windowTemplateUrl: modalOptions.windowTemplateUrl,
37950 size: modalOptions.size,
37951 openedClass: modalOptions.openedClass,
37952 appendTo: modalOptions.appendTo
37954 modalOpenedDeferred.resolve(true);
37956 }, function resolveError(reason) {
37957 modalOpenedDeferred.reject(reason);
37958 modalResultDeferred.reject(reason);
37959 })['finally'](function() {
37960 if (promiseChain === samePromise) {
37961 promiseChain = null;
37965 return modalInstance;
37973 return $modalProvider;
37976 angular.module('ui.bootstrap.paging', [])
37978 * Helper internal service for generating common controller code between the
37979 * pager and pagination components
37981 .factory('uibPaging', ['$parse', function($parse) {
37983 create: function(ctrl, $scope, $attrs) {
37984 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
37985 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
37987 ctrl.init = function(ngModelCtrl, config) {
37988 ctrl.ngModelCtrl = ngModelCtrl;
37989 ctrl.config = config;
37991 ngModelCtrl.$render = function() {
37995 if ($attrs.itemsPerPage) {
37996 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
37997 ctrl.itemsPerPage = parseInt(value, 10);
37998 $scope.totalPages = ctrl.calculateTotalPages();
38002 ctrl.itemsPerPage = config.itemsPerPage;
38005 $scope.$watch('totalItems', function(newTotal, oldTotal) {
38006 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
38007 $scope.totalPages = ctrl.calculateTotalPages();
38013 ctrl.calculateTotalPages = function() {
38014 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
38015 return Math.max(totalPages || 0, 1);
38018 ctrl.render = function() {
38019 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
38022 $scope.selectPage = function(page, evt) {
38024 evt.preventDefault();
38027 var clickAllowed = !$scope.ngDisabled || !evt;
38028 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
38029 if (evt && evt.target) {
38032 ctrl.ngModelCtrl.$setViewValue(page);
38033 ctrl.ngModelCtrl.$render();
38037 $scope.getText = function(key) {
38038 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
38041 $scope.noPrevious = function() {
38042 return $scope.page === 1;
38045 $scope.noNext = function() {
38046 return $scope.page === $scope.totalPages;
38049 ctrl.updatePage = function() {
38050 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
38052 if ($scope.page > $scope.totalPages) {
38053 $scope.selectPage($scope.totalPages);
38055 ctrl.ngModelCtrl.$render();
38062 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
38064 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
38065 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
38067 uibPaging.create(this, $scope, $attrs);
38070 .constant('uibPagerConfig', {
38072 previousText: '« Previous',
38073 nextText: 'Next »',
38077 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
38085 require: ['uibPager', '?ngModel'],
38086 controller: 'UibPagerController',
38087 controllerAs: 'pager',
38088 templateUrl: function(element, attrs) {
38089 return attrs.templateUrl || 'uib/template/pager/pager.html';
38092 link: function(scope, element, attrs, ctrls) {
38093 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38095 if (!ngModelCtrl) {
38096 return; // do nothing if no ng-model
38099 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
38104 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
38105 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
38107 // Setup configuration parameters
38108 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
38109 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
38110 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
38111 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers;
38112 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
38113 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
38115 uibPaging.create(this, $scope, $attrs);
38117 if ($attrs.maxSize) {
38118 $scope.$parent.$watch($parse($attrs.maxSize), function(value) {
38119 maxSize = parseInt(value, 10);
38124 // Create page object used in template
38125 function makePage(number, text, isActive) {
38133 function getPages(currentPage, totalPages) {
38136 // Default page limits
38137 var startPage = 1, endPage = totalPages;
38138 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
38140 // recompute if maxSize
38143 // Current page is displayed in the middle of the visible ones
38144 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
38145 endPage = startPage + maxSize - 1;
38147 // Adjust if limit is exceeded
38148 if (endPage > totalPages) {
38149 endPage = totalPages;
38150 startPage = endPage - maxSize + 1;
38153 // Visible pages are paginated with maxSize
38154 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
38156 // Adjust last page if limit is exceeded
38157 endPage = Math.min(startPage + maxSize - 1, totalPages);
38161 // Add page number links
38162 for (var number = startPage; number <= endPage; number++) {
38163 var page = makePage(number, number, number === currentPage);
38167 // Add links to move between page sets
38168 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
38169 if (startPage > 1) {
38170 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
38171 var previousPageSet = makePage(startPage - 1, '...', false);
38172 pages.unshift(previousPageSet);
38174 if (boundaryLinkNumbers) {
38175 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
38176 var secondPageLink = makePage(2, '2', false);
38177 pages.unshift(secondPageLink);
38179 //add the first page
38180 var firstPageLink = makePage(1, '1', false);
38181 pages.unshift(firstPageLink);
38185 if (endPage < totalPages) {
38186 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
38187 var nextPageSet = makePage(endPage + 1, '...', false);
38188 pages.push(nextPageSet);
38190 if (boundaryLinkNumbers) {
38191 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
38192 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
38193 pages.push(secondToLastPageLink);
38195 //add the last page
38196 var lastPageLink = makePage(totalPages, totalPages, false);
38197 pages.push(lastPageLink);
38204 var originalRender = this.render;
38205 this.render = function() {
38207 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
38208 $scope.pages = getPages($scope.page, $scope.totalPages);
38213 .constant('uibPaginationConfig', {
38215 boundaryLinks: false,
38216 boundaryLinkNumbers: false,
38217 directionLinks: true,
38218 firstText: 'First',
38219 previousText: 'Previous',
38223 forceEllipses: false
38226 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
38236 require: ['uibPagination', '?ngModel'],
38237 controller: 'UibPaginationController',
38238 controllerAs: 'pagination',
38239 templateUrl: function(element, attrs) {
38240 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
38243 link: function(scope, element, attrs, ctrls) {
38244 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38246 if (!ngModelCtrl) {
38247 return; // do nothing if no ng-model
38250 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
38256 * The following features are still outstanding: animation as a
38257 * function, placement as a function, inside, support for more triggers than
38258 * just mouse enter/leave, html tooltips, and selector delegation.
38260 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
38263 * The $tooltip service creates tooltip- and popover-like directives as well as
38264 * houses global options for them.
38266 .provider('$uibTooltip', function() {
38267 // The default options tooltip and popover.
38268 var defaultOptions = {
38270 placementClassPrefix: '',
38273 popupCloseDelay: 0,
38274 useContentExp: false
38277 // Default hide triggers for each show trigger
38279 'mouseenter': 'mouseleave',
38281 'outsideClick': 'outsideClick',
38286 // The options specified to the provider globally.
38287 var globalOptions = {};
38290 * `options({})` allows global configuration of all tooltips in the
38293 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
38294 * // place tooltips left instead of top by default
38295 * $tooltipProvider.options( { placement: 'left' } );
38298 this.options = function(value) {
38299 angular.extend(globalOptions, value);
38303 * This allows you to extend the set of trigger mappings available. E.g.:
38305 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
38307 this.setTriggers = function setTriggers(triggers) {
38308 angular.extend(triggerMap, triggers);
38312 * This is a helper function for translating camel-case to snake_case.
38314 function snake_case(name) {
38315 var regexp = /[A-Z]/g;
38316 var separator = '-';
38317 return name.replace(regexp, function(letter, pos) {
38318 return (pos ? separator : '') + letter.toLowerCase();
38323 * Returns the actual instance of the $tooltip service.
38324 * TODO support multiple triggers
38326 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
38327 var openedTooltips = $$stackedMap.createNew();
38328 $document.on('keypress', keypressListener);
38330 $rootScope.$on('$destroy', function() {
38331 $document.off('keypress', keypressListener);
38334 function keypressListener(e) {
38335 if (e.which === 27) {
38336 var last = openedTooltips.top();
38338 last.value.close();
38339 openedTooltips.removeTop();
38345 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
38346 options = angular.extend({}, defaultOptions, globalOptions, options);
38349 * Returns an object of show and hide triggers.
38351 * If a trigger is supplied,
38352 * it is used to show the tooltip; otherwise, it will use the `trigger`
38353 * option passed to the `$tooltipProvider.options` method; else it will
38354 * default to the trigger supplied to this directive factory.
38356 * The hide trigger is based on the show trigger. If the `trigger` option
38357 * was passed to the `$tooltipProvider.options` method, it will use the
38358 * mapped trigger from `triggerMap` or the passed trigger if the map is
38359 * undefined; otherwise, it uses the `triggerMap` value of the show
38360 * trigger; else it will just use the show trigger.
38362 function getTriggers(trigger) {
38363 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
38364 var hide = show.map(function(trigger) {
38365 return triggerMap[trigger] || trigger;
38373 var directiveName = snake_case(ttType);
38375 var startSym = $interpolate.startSymbol();
38376 var endSym = $interpolate.endSymbol();
38378 '<div '+ directiveName + '-popup '+
38379 'title="' + startSym + 'title' + endSym + '" '+
38380 (options.useContentExp ?
38381 'content-exp="contentExp()" ' :
38382 'content="' + startSym + 'content' + endSym + '" ') +
38383 'placement="' + startSym + 'placement' + endSym + '" '+
38384 'popup-class="' + startSym + 'popupClass' + endSym + '" '+
38385 'animation="animation" ' +
38386 'is-open="isOpen"' +
38387 'origin-scope="origScope" ' +
38388 'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
38393 compile: function(tElem, tAttrs) {
38394 var tooltipLinker = $compile(template);
38396 return function link(scope, element, attrs, tooltipCtrl) {
38398 var tooltipLinkedScope;
38399 var transitionTimeout;
38402 var positionTimeout;
38403 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
38404 var triggers = getTriggers(undefined);
38405 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
38406 var ttScope = scope.$new(true);
38407 var repositionScheduled = false;
38408 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
38409 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
38410 var observers = [];
38412 var positionTooltip = function() {
38413 // check if tooltip exists and is not empty
38414 if (!tooltip || !tooltip.html()) { return; }
38416 if (!positionTimeout) {
38417 positionTimeout = $timeout(function() {
38418 // Reset the positioning.
38419 tooltip.css({ top: 0, left: 0 });
38421 // Now set the calculated positioning.
38422 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
38423 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' });
38425 // If the placement class is prefixed, still need
38426 // to remove the TWBS standard class.
38427 if (options.placementClassPrefix) {
38428 tooltip.removeClass('top bottom left right');
38431 tooltip.removeClass(
38432 options.placementClassPrefix + 'top ' +
38433 options.placementClassPrefix + 'top-left ' +
38434 options.placementClassPrefix + 'top-right ' +
38435 options.placementClassPrefix + 'bottom ' +
38436 options.placementClassPrefix + 'bottom-left ' +
38437 options.placementClassPrefix + 'bottom-right ' +
38438 options.placementClassPrefix + 'left ' +
38439 options.placementClassPrefix + 'left-top ' +
38440 options.placementClassPrefix + 'left-bottom ' +
38441 options.placementClassPrefix + 'right ' +
38442 options.placementClassPrefix + 'right-top ' +
38443 options.placementClassPrefix + 'right-bottom');
38445 var placement = ttPosition.placement.split('-');
38446 tooltip.addClass(placement[0], options.placementClassPrefix + ttPosition.placement);
38447 $position.positionArrow(tooltip, ttPosition.placement);
38449 positionTimeout = null;
38454 // Set up the correct scope to allow transclusion later
38455 ttScope.origScope = scope;
38457 // By default, the tooltip is not open.
38458 // TODO add ability to start tooltip opened
38459 ttScope.isOpen = false;
38460 openedTooltips.add(ttScope, {
38464 function toggleTooltipBind() {
38465 if (!ttScope.isOpen) {
38472 // Show the tooltip with delay if specified, otherwise show it immediately
38473 function showTooltipBind() {
38474 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
38481 if (ttScope.popupDelay) {
38482 // Do nothing if the tooltip was already scheduled to pop-up.
38483 // This happens if show is triggered multiple times before any hide is triggered.
38484 if (!showTimeout) {
38485 showTimeout = $timeout(show, ttScope.popupDelay, false);
38492 function hideTooltipBind() {
38495 if (ttScope.popupCloseDelay) {
38496 if (!hideTimeout) {
38497 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
38504 // Show the tooltip popup element.
38509 // Don't show empty tooltips.
38510 if (!ttScope.content) {
38511 return angular.noop;
38516 // And show the tooltip.
38517 ttScope.$evalAsync(function() {
38518 ttScope.isOpen = true;
38519 assignIsOpen(true);
38524 function cancelShow() {
38526 $timeout.cancel(showTimeout);
38527 showTimeout = null;
38530 if (positionTimeout) {
38531 $timeout.cancel(positionTimeout);
38532 positionTimeout = null;
38536 // Hide the tooltip popup element.
38542 // First things first: we don't show it anymore.
38543 ttScope.$evalAsync(function() {
38544 ttScope.isOpen = false;
38545 assignIsOpen(false);
38546 // And now we remove it from the DOM. However, if we have animation, we
38547 // need to wait for it to expire beforehand.
38548 // FIXME: this is a placeholder for a port of the transitions library.
38549 // The fade transition in TWBS is 150ms.
38550 if (ttScope.animation) {
38551 if (!transitionTimeout) {
38552 transitionTimeout = $timeout(removeTooltip, 150, false);
38560 function cancelHide() {
38562 $timeout.cancel(hideTimeout);
38563 hideTimeout = null;
38565 if (transitionTimeout) {
38566 $timeout.cancel(transitionTimeout);
38567 transitionTimeout = null;
38571 function createTooltip() {
38572 // There can only be one tooltip element per directive shown at once.
38577 tooltipLinkedScope = ttScope.$new();
38578 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
38579 if (appendToBody) {
38580 $document.find('body').append(tooltip);
38582 element.after(tooltip);
38589 function removeTooltip() {
38592 unregisterObservers();
38598 if (tooltipLinkedScope) {
38599 tooltipLinkedScope.$destroy();
38600 tooltipLinkedScope = null;
38605 * Set the initial scope values. Once
38606 * the tooltip is created, the observers
38607 * will be added to keep things in sync.
38609 function prepareTooltip() {
38610 ttScope.title = attrs[prefix + 'Title'];
38611 if (contentParse) {
38612 ttScope.content = contentParse(scope);
38614 ttScope.content = attrs[ttType];
38617 ttScope.popupClass = attrs[prefix + 'Class'];
38618 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
38620 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
38621 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
38622 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
38623 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
38626 function assignIsOpen(isOpen) {
38627 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
38628 isOpenParse.assign(scope, isOpen);
38632 ttScope.contentExp = function() {
38633 return ttScope.content;
38637 * Observe the relevant attributes.
38639 attrs.$observe('disabled', function(val) {
38644 if (val && ttScope.isOpen) {
38650 scope.$watch(isOpenParse, function(val) {
38651 if (ttScope && !val === ttScope.isOpen) {
38652 toggleTooltipBind();
38657 function prepObservers() {
38658 observers.length = 0;
38660 if (contentParse) {
38662 scope.$watch(contentParse, function(val) {
38663 ttScope.content = val;
38664 if (!val && ttScope.isOpen) {
38671 tooltipLinkedScope.$watch(function() {
38672 if (!repositionScheduled) {
38673 repositionScheduled = true;
38674 tooltipLinkedScope.$$postDigest(function() {
38675 repositionScheduled = false;
38676 if (ttScope && ttScope.isOpen) {
38685 attrs.$observe(ttType, function(val) {
38686 ttScope.content = val;
38687 if (!val && ttScope.isOpen) {
38697 attrs.$observe(prefix + 'Title', function(val) {
38698 ttScope.title = val;
38699 if (ttScope.isOpen) {
38706 attrs.$observe(prefix + 'Placement', function(val) {
38707 ttScope.placement = val ? val : options.placement;
38708 if (ttScope.isOpen) {
38715 function unregisterObservers() {
38716 if (observers.length) {
38717 angular.forEach(observers, function(observer) {
38720 observers.length = 0;
38724 // hide tooltips/popovers for outsideClick trigger
38725 function bodyHideTooltipBind(e) {
38726 if (!ttScope || !ttScope.isOpen || !tooltip) {
38729 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
38730 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
38735 var unregisterTriggers = function() {
38736 triggers.show.forEach(function(trigger) {
38737 if (trigger === 'outsideClick') {
38738 element.off('click', toggleTooltipBind);
38740 element.off(trigger, showTooltipBind);
38741 element.off(trigger, toggleTooltipBind);
38744 triggers.hide.forEach(function(trigger) {
38745 if (trigger === 'outsideClick') {
38746 $document.off('click', bodyHideTooltipBind);
38748 element.off(trigger, hideTooltipBind);
38753 function prepTriggers() {
38754 var val = attrs[prefix + 'Trigger'];
38755 unregisterTriggers();
38757 triggers = getTriggers(val);
38759 if (triggers.show !== 'none') {
38760 triggers.show.forEach(function(trigger, idx) {
38761 if (trigger === 'outsideClick') {
38762 element.on('click', toggleTooltipBind);
38763 $document.on('click', bodyHideTooltipBind);
38764 } else if (trigger === triggers.hide[idx]) {
38765 element.on(trigger, toggleTooltipBind);
38766 } else if (trigger) {
38767 element.on(trigger, showTooltipBind);
38768 element.on(triggers.hide[idx], hideTooltipBind);
38771 element.on('keypress', function(e) {
38772 if (e.which === 27) {
38782 var animation = scope.$eval(attrs[prefix + 'Animation']);
38783 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
38785 var appendToBodyVal;
38786 var appendKey = prefix + 'AppendToBody';
38787 if (appendKey in attrs && attrs[appendKey] === undefined) {
38788 appendToBodyVal = true;
38790 appendToBodyVal = scope.$eval(attrs[appendKey]);
38793 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
38795 // if a tooltip is attached to <body> we need to remove it on
38796 // location change as its parent scope will probably not be destroyed
38798 if (appendToBody) {
38799 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
38800 if (ttScope.isOpen) {
38806 // Make sure tooltip is destroyed and removed.
38807 scope.$on('$destroy', function onDestroyTooltip() {
38808 unregisterTriggers();
38810 openedTooltips.remove(ttScope);
38820 // This is mostly ngInclude code but with a custom scope
38821 .directive('uibTooltipTemplateTransclude', [
38822 '$animate', '$sce', '$compile', '$templateRequest',
38823 function ($animate, $sce, $compile, $templateRequest) {
38825 link: function(scope, elem, attrs) {
38826 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
38828 var changeCounter = 0,
38833 var cleanupLastIncludeContent = function() {
38834 if (previousElement) {
38835 previousElement.remove();
38836 previousElement = null;
38839 if (currentScope) {
38840 currentScope.$destroy();
38841 currentScope = null;
38844 if (currentElement) {
38845 $animate.leave(currentElement).then(function() {
38846 previousElement = null;
38848 previousElement = currentElement;
38849 currentElement = null;
38853 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
38854 var thisChangeId = ++changeCounter;
38857 //set the 2nd param to true to ignore the template request error so that the inner
38858 //contents and scope can be cleaned up.
38859 $templateRequest(src, true).then(function(response) {
38860 if (thisChangeId !== changeCounter) { return; }
38861 var newScope = origScope.$new();
38862 var template = response;
38864 var clone = $compile(template)(newScope, function(clone) {
38865 cleanupLastIncludeContent();
38866 $animate.enter(clone, elem);
38869 currentScope = newScope;
38870 currentElement = clone;
38872 currentScope.$emit('$includeContentLoaded', src);
38874 if (thisChangeId === changeCounter) {
38875 cleanupLastIncludeContent();
38876 scope.$emit('$includeContentError', src);
38879 scope.$emit('$includeContentRequested', src);
38881 cleanupLastIncludeContent();
38885 scope.$on('$destroy', cleanupLastIncludeContent);
38891 * Note that it's intentional that these classes are *not* applied through $animate.
38892 * They must not be animated as they're expected to be present on the tooltip on
38895 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
38898 link: function(scope, element, attrs) {
38899 // need to set the primary position so the
38900 // arrow has space during position measure.
38901 // tooltip.positionTooltip()
38902 if (scope.placement) {
38903 // // There are no top-left etc... classes
38904 // // in TWBS, so we need the primary position.
38905 var position = $uibPosition.parsePlacement(scope.placement);
38906 element.addClass(position[0]);
38908 element.addClass('top');
38911 if (scope.popupClass) {
38912 element.addClass(scope.popupClass);
38915 if (scope.animation()) {
38916 element.addClass(attrs.tooltipAnimationClass);
38922 .directive('uibTooltipPopup', function() {
38925 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38926 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
38930 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
38931 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
38934 .directive('uibTooltipTemplatePopup', function() {
38937 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38938 originScope: '&' },
38939 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
38943 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
38944 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
38945 useContentExp: true
38949 .directive('uibTooltipHtmlPopup', function() {
38952 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38953 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
38957 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
38958 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
38959 useContentExp: true
38964 * The following features are still outstanding: popup delay, animation as a
38965 * function, placement as a function, inside, support for more triggers than
38966 * just mouse enter/leave, and selector delegatation.
38968 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
38970 .directive('uibPopoverTemplatePopup', function() {
38973 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38974 originScope: '&' },
38975 templateUrl: 'uib/template/popover/popover-template.html'
38979 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
38980 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
38981 useContentExp: true
38985 .directive('uibPopoverHtmlPopup', function() {
38988 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38989 templateUrl: 'uib/template/popover/popover-html.html'
38993 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
38994 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
38995 useContentExp: true
38999 .directive('uibPopoverPopup', function() {
39002 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39003 templateUrl: 'uib/template/popover/popover.html'
39007 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
39008 return $uibTooltip('uibPopover', 'popover', 'click');
39011 angular.module('ui.bootstrap.progressbar', [])
39013 .constant('uibProgressConfig', {
39018 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
39020 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
39023 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
39025 this.addBar = function(bar, element, attrs) {
39027 element.css({'transition': 'none'});
39030 this.bars.push(bar);
39032 bar.max = $scope.max;
39033 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
39035 bar.$watch('value', function(value) {
39036 bar.recalculatePercentage();
39039 bar.recalculatePercentage = function() {
39040 var totalPercentage = self.bars.reduce(function(total, bar) {
39041 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
39042 return total + bar.percent;
39045 if (totalPercentage > 100) {
39046 bar.percent -= totalPercentage - 100;
39050 bar.$on('$destroy', function() {
39052 self.removeBar(bar);
39056 this.removeBar = function(bar) {
39057 this.bars.splice(this.bars.indexOf(bar), 1);
39058 this.bars.forEach(function (bar) {
39059 bar.recalculatePercentage();
39063 $scope.$watch('max', function(max) {
39064 self.bars.forEach(function(bar) {
39065 bar.max = $scope.max;
39066 bar.recalculatePercentage();
39071 .directive('uibProgress', function() {
39075 controller: 'UibProgressController',
39076 require: 'uibProgress',
39080 templateUrl: 'uib/template/progressbar/progress.html'
39084 .directive('uibBar', function() {
39088 require: '^uibProgress',
39093 templateUrl: 'uib/template/progressbar/bar.html',
39094 link: function(scope, element, attrs, progressCtrl) {
39095 progressCtrl.addBar(scope, element, attrs);
39100 .directive('uibProgressbar', function() {
39104 controller: 'UibProgressController',
39110 templateUrl: 'uib/template/progressbar/progressbar.html',
39111 link: function(scope, element, attrs, progressCtrl) {
39112 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
39117 angular.module('ui.bootstrap.rating', [])
39119 .constant('uibRatingConfig', {
39123 titles : ['one', 'two', 'three', 'four', 'five']
39126 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
39127 var ngModelCtrl = { $setViewValue: angular.noop };
39129 this.init = function(ngModelCtrl_) {
39130 ngModelCtrl = ngModelCtrl_;
39131 ngModelCtrl.$render = this.render;
39133 ngModelCtrl.$formatters.push(function(value) {
39134 if (angular.isNumber(value) && value << 0 !== value) {
39135 value = Math.round(value);
39141 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
39142 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
39143 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
39144 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
39145 tmpTitles : ratingConfig.titles;
39147 var ratingStates = angular.isDefined($attrs.ratingStates) ?
39148 $scope.$parent.$eval($attrs.ratingStates) :
39149 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
39150 $scope.range = this.buildTemplateObjects(ratingStates);
39153 this.buildTemplateObjects = function(states) {
39154 for (var i = 0, n = states.length; i < n; i++) {
39155 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
39160 this.getTitle = function(index) {
39161 if (index >= this.titles.length) {
39165 return this.titles[index];
39168 $scope.rate = function(value) {
39169 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
39170 ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
39171 ngModelCtrl.$render();
39175 $scope.enter = function(value) {
39176 if (!$scope.readonly) {
39177 $scope.value = value;
39179 $scope.onHover({value: value});
39182 $scope.reset = function() {
39183 $scope.value = ngModelCtrl.$viewValue;
39187 $scope.onKeydown = function(evt) {
39188 if (/(37|38|39|40)/.test(evt.which)) {
39189 evt.preventDefault();
39190 evt.stopPropagation();
39191 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
39195 this.render = function() {
39196 $scope.value = ngModelCtrl.$viewValue;
39200 .directive('uibRating', function() {
39202 require: ['uibRating', 'ngModel'],
39208 controller: 'UibRatingController',
39209 templateUrl: 'uib/template/rating/rating.html',
39211 link: function(scope, element, attrs, ctrls) {
39212 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39213 ratingCtrl.init(ngModelCtrl);
39218 angular.module('ui.bootstrap.tabs', [])
39220 .controller('UibTabsetController', ['$scope', function ($scope) {
39222 tabs = ctrl.tabs = $scope.tabs = [];
39224 ctrl.select = function(selectedTab) {
39225 angular.forEach(tabs, function(tab) {
39226 if (tab.active && tab !== selectedTab) {
39227 tab.active = false;
39229 selectedTab.selectCalled = false;
39232 selectedTab.active = true;
39233 // only call select if it has not already been called
39234 if (!selectedTab.selectCalled) {
39235 selectedTab.onSelect();
39236 selectedTab.selectCalled = true;
39240 ctrl.addTab = function addTab(tab) {
39242 // we can't run the select function on the first tab
39243 // since that would select it twice
39244 if (tabs.length === 1 && tab.active !== false) {
39246 } else if (tab.active) {
39249 tab.active = false;
39253 ctrl.removeTab = function removeTab(tab) {
39254 var index = tabs.indexOf(tab);
39255 //Select a new tab if the tab to be removed is selected and not destroyed
39256 if (tab.active && tabs.length > 1 && !destroyed) {
39257 //If this is the last tab, select the previous tab. else, the next tab.
39258 var newActiveIndex = index === tabs.length - 1 ? index - 1 : index + 1;
39259 ctrl.select(tabs[newActiveIndex]);
39261 tabs.splice(index, 1);
39265 $scope.$on('$destroy', function() {
39270 .directive('uibTabset', function() {
39277 controller: 'UibTabsetController',
39278 templateUrl: 'uib/template/tabs/tabset.html',
39279 link: function(scope, element, attrs) {
39280 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
39281 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
39286 .directive('uibTab', ['$parse', function($parse) {
39288 require: '^uibTabset',
39290 templateUrl: 'uib/template/tabs/tab.html',
39295 onSelect: '&select', //This callback is called in contentHeadingTransclude
39296 //once it inserts the tab's content into the dom
39297 onDeselect: '&deselect'
39299 controller: function() {
39300 //Empty controller so other directives can require being 'under' a tab
39302 controllerAs: 'tab',
39303 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
39304 scope.$watch('active', function(active) {
39306 tabsetCtrl.select(scope);
39310 scope.disabled = false;
39311 if (attrs.disable) {
39312 scope.$parent.$watch($parse(attrs.disable), function(value) {
39313 scope.disabled = !! value;
39317 scope.select = function() {
39318 if (!scope.disabled) {
39319 scope.active = true;
39323 tabsetCtrl.addTab(scope);
39324 scope.$on('$destroy', function() {
39325 tabsetCtrl.removeTab(scope);
39328 //We need to transclude later, once the content container is ready.
39329 //when this link happens, we're inside a tab heading.
39330 scope.$transcludeFn = transclude;
39335 .directive('uibTabHeadingTransclude', function() {
39338 require: '^uibTab',
39339 link: function(scope, elm) {
39340 scope.$watch('headingElement', function updateHeadingElement(heading) {
39343 elm.append(heading);
39350 .directive('uibTabContentTransclude', function() {
39353 require: '^uibTabset',
39354 link: function(scope, elm, attrs) {
39355 var tab = scope.$eval(attrs.uibTabContentTransclude);
39357 //Now our tab is ready to be transcluded: both the tab heading area
39358 //and the tab content area are loaded. Transclude 'em both.
39359 tab.$transcludeFn(tab.$parent, function(contents) {
39360 angular.forEach(contents, function(node) {
39361 if (isTabHeading(node)) {
39362 //Let tabHeadingTransclude know.
39363 tab.headingElement = node;
39372 function isTabHeading(node) {
39373 return node.tagName && (
39374 node.hasAttribute('uib-tab-heading') ||
39375 node.hasAttribute('data-uib-tab-heading') ||
39376 node.hasAttribute('x-uib-tab-heading') ||
39377 node.tagName.toLowerCase() === 'uib-tab-heading' ||
39378 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
39379 node.tagName.toLowerCase() === 'x-uib-tab-heading'
39384 angular.module('ui.bootstrap.timepicker', [])
39386 .constant('uibTimepickerConfig', {
39390 showMeridian: true,
39391 showSeconds: false,
39393 readonlyInput: false,
39399 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
39400 var selected = new Date(),
39401 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
39402 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
39404 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
39405 $element.removeAttr('tabindex');
39407 this.init = function(ngModelCtrl_, inputs) {
39408 ngModelCtrl = ngModelCtrl_;
39409 ngModelCtrl.$render = this.render;
39411 ngModelCtrl.$formatters.unshift(function(modelValue) {
39412 return modelValue ? new Date(modelValue) : null;
39415 var hoursInputEl = inputs.eq(0),
39416 minutesInputEl = inputs.eq(1),
39417 secondsInputEl = inputs.eq(2);
39419 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
39422 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39425 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
39427 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39430 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
39431 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39434 var hourStep = timepickerConfig.hourStep;
39435 if ($attrs.hourStep) {
39436 $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
39437 hourStep = parseInt(value, 10);
39441 var minuteStep = timepickerConfig.minuteStep;
39442 if ($attrs.minuteStep) {
39443 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
39444 minuteStep = parseInt(value, 10);
39449 $scope.$parent.$watch($parse($attrs.min), function(value) {
39450 var dt = new Date(value);
39451 min = isNaN(dt) ? undefined : dt;
39455 $scope.$parent.$watch($parse($attrs.max), function(value) {
39456 var dt = new Date(value);
39457 max = isNaN(dt) ? undefined : dt;
39460 var disabled = false;
39461 if ($attrs.ngDisabled) {
39462 $scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
39467 $scope.noIncrementHours = function() {
39468 var incrementedSelected = addMinutes(selected, hourStep * 60);
39469 return disabled || incrementedSelected > max ||
39470 incrementedSelected < selected && incrementedSelected < min;
39473 $scope.noDecrementHours = function() {
39474 var decrementedSelected = addMinutes(selected, -hourStep * 60);
39475 return disabled || decrementedSelected < min ||
39476 decrementedSelected > selected && decrementedSelected > max;
39479 $scope.noIncrementMinutes = function() {
39480 var incrementedSelected = addMinutes(selected, minuteStep);
39481 return disabled || incrementedSelected > max ||
39482 incrementedSelected < selected && incrementedSelected < min;
39485 $scope.noDecrementMinutes = function() {
39486 var decrementedSelected = addMinutes(selected, -minuteStep);
39487 return disabled || decrementedSelected < min ||
39488 decrementedSelected > selected && decrementedSelected > max;
39491 $scope.noIncrementSeconds = function() {
39492 var incrementedSelected = addSeconds(selected, secondStep);
39493 return disabled || incrementedSelected > max ||
39494 incrementedSelected < selected && incrementedSelected < min;
39497 $scope.noDecrementSeconds = function() {
39498 var decrementedSelected = addSeconds(selected, -secondStep);
39499 return disabled || decrementedSelected < min ||
39500 decrementedSelected > selected && decrementedSelected > max;
39503 $scope.noToggleMeridian = function() {
39504 if (selected.getHours() < 12) {
39505 return disabled || addMinutes(selected, 12 * 60) > max;
39508 return disabled || addMinutes(selected, -12 * 60) < min;
39511 var secondStep = timepickerConfig.secondStep;
39512 if ($attrs.secondStep) {
39513 $scope.$parent.$watch($parse($attrs.secondStep), function(value) {
39514 secondStep = parseInt(value, 10);
39518 $scope.showSeconds = timepickerConfig.showSeconds;
39519 if ($attrs.showSeconds) {
39520 $scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
39521 $scope.showSeconds = !!value;
39526 $scope.showMeridian = timepickerConfig.showMeridian;
39527 if ($attrs.showMeridian) {
39528 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
39529 $scope.showMeridian = !!value;
39531 if (ngModelCtrl.$error.time) {
39532 // Evaluate from template
39533 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
39534 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39535 selected.setHours(hours);
39544 // Get $scope.hours in 24H mode if valid
39545 function getHoursFromTemplate() {
39546 var hours = parseInt($scope.hours, 10);
39547 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
39548 hours >= 0 && hours < 24;
39553 if ($scope.showMeridian) {
39554 if (hours === 12) {
39557 if ($scope.meridian === meridians[1]) {
39558 hours = hours + 12;
39564 function getMinutesFromTemplate() {
39565 var minutes = parseInt($scope.minutes, 10);
39566 return minutes >= 0 && minutes < 60 ? minutes : undefined;
39569 function getSecondsFromTemplate() {
39570 var seconds = parseInt($scope.seconds, 10);
39571 return seconds >= 0 && seconds < 60 ? seconds : undefined;
39574 function pad(value) {
39575 if (value === null) {
39579 return angular.isDefined(value) && value.toString().length < 2 ?
39580 '0' + value : value.toString();
39583 // Respond on mousewheel spin
39584 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39585 var isScrollingUp = function(e) {
39586 if (e.originalEvent) {
39587 e = e.originalEvent;
39589 //pick correct delta variable depending on event
39590 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
39591 return e.detail || delta > 0;
39594 hoursInputEl.bind('mousewheel wheel', function(e) {
39596 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
39598 e.preventDefault();
39601 minutesInputEl.bind('mousewheel wheel', function(e) {
39603 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
39605 e.preventDefault();
39608 secondsInputEl.bind('mousewheel wheel', function(e) {
39610 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
39612 e.preventDefault();
39616 // Respond on up/down arrowkeys
39617 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39618 hoursInputEl.bind('keydown', function(e) {
39620 if (e.which === 38) { // up
39621 e.preventDefault();
39622 $scope.incrementHours();
39624 } else if (e.which === 40) { // down
39625 e.preventDefault();
39626 $scope.decrementHours();
39632 minutesInputEl.bind('keydown', function(e) {
39634 if (e.which === 38) { // up
39635 e.preventDefault();
39636 $scope.incrementMinutes();
39638 } else if (e.which === 40) { // down
39639 e.preventDefault();
39640 $scope.decrementMinutes();
39646 secondsInputEl.bind('keydown', function(e) {
39648 if (e.which === 38) { // up
39649 e.preventDefault();
39650 $scope.incrementSeconds();
39652 } else if (e.which === 40) { // down
39653 e.preventDefault();
39654 $scope.decrementSeconds();
39661 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39662 if ($scope.readonlyInput) {
39663 $scope.updateHours = angular.noop;
39664 $scope.updateMinutes = angular.noop;
39665 $scope.updateSeconds = angular.noop;
39669 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
39670 ngModelCtrl.$setViewValue(null);
39671 ngModelCtrl.$setValidity('time', false);
39672 if (angular.isDefined(invalidHours)) {
39673 $scope.invalidHours = invalidHours;
39676 if (angular.isDefined(invalidMinutes)) {
39677 $scope.invalidMinutes = invalidMinutes;
39680 if (angular.isDefined(invalidSeconds)) {
39681 $scope.invalidSeconds = invalidSeconds;
39685 $scope.updateHours = function() {
39686 var hours = getHoursFromTemplate(),
39687 minutes = getMinutesFromTemplate();
39689 ngModelCtrl.$setDirty();
39691 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39692 selected.setHours(hours);
39693 selected.setMinutes(minutes);
39694 if (selected < min || selected > max) {
39704 hoursInputEl.bind('blur', function(e) {
39705 ngModelCtrl.$setTouched();
39706 if ($scope.hours === null || $scope.hours === '') {
39708 } else if (!$scope.invalidHours && $scope.hours < 10) {
39709 $scope.$apply(function() {
39710 $scope.hours = pad($scope.hours);
39715 $scope.updateMinutes = function() {
39716 var minutes = getMinutesFromTemplate(),
39717 hours = getHoursFromTemplate();
39719 ngModelCtrl.$setDirty();
39721 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39722 selected.setHours(hours);
39723 selected.setMinutes(minutes);
39724 if (selected < min || selected > max) {
39725 invalidate(undefined, true);
39730 invalidate(undefined, true);
39734 minutesInputEl.bind('blur', function(e) {
39735 ngModelCtrl.$setTouched();
39736 if ($scope.minutes === null) {
39737 invalidate(undefined, true);
39738 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
39739 $scope.$apply(function() {
39740 $scope.minutes = pad($scope.minutes);
39745 $scope.updateSeconds = function() {
39746 var seconds = getSecondsFromTemplate();
39748 ngModelCtrl.$setDirty();
39750 if (angular.isDefined(seconds)) {
39751 selected.setSeconds(seconds);
39754 invalidate(undefined, undefined, true);
39758 secondsInputEl.bind('blur', function(e) {
39759 if (!$scope.invalidSeconds && $scope.seconds < 10) {
39760 $scope.$apply( function() {
39761 $scope.seconds = pad($scope.seconds);
39768 this.render = function() {
39769 var date = ngModelCtrl.$viewValue;
39772 ngModelCtrl.$setValidity('time', false);
39773 $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
39779 if (selected < min || selected > max) {
39780 ngModelCtrl.$setValidity('time', false);
39781 $scope.invalidHours = true;
39782 $scope.invalidMinutes = true;
39790 // Call internally when we know that model is valid.
39791 function refresh(keyboardChange) {
39793 ngModelCtrl.$setViewValue(new Date(selected));
39794 updateTemplate(keyboardChange);
39797 function makeValid() {
39798 ngModelCtrl.$setValidity('time', true);
39799 $scope.invalidHours = false;
39800 $scope.invalidMinutes = false;
39801 $scope.invalidSeconds = false;
39804 function updateTemplate(keyboardChange) {
39805 if (!ngModelCtrl.$modelValue) {
39806 $scope.hours = null;
39807 $scope.minutes = null;
39808 $scope.seconds = null;
39809 $scope.meridian = meridians[0];
39811 var hours = selected.getHours(),
39812 minutes = selected.getMinutes(),
39813 seconds = selected.getSeconds();
39815 if ($scope.showMeridian) {
39816 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
39819 $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
39820 if (keyboardChange !== 'm') {
39821 $scope.minutes = pad(minutes);
39823 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39825 if (keyboardChange !== 's') {
39826 $scope.seconds = pad(seconds);
39828 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39832 function addSecondsToSelected(seconds) {
39833 selected = addSeconds(selected, seconds);
39837 function addMinutes(selected, minutes) {
39838 return addSeconds(selected, minutes*60);
39841 function addSeconds(date, seconds) {
39842 var dt = new Date(date.getTime() + seconds * 1000);
39843 var newDate = new Date(date);
39844 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
39848 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
39849 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
39851 $scope.incrementHours = function() {
39852 if (!$scope.noIncrementHours()) {
39853 addSecondsToSelected(hourStep * 60 * 60);
39857 $scope.decrementHours = function() {
39858 if (!$scope.noDecrementHours()) {
39859 addSecondsToSelected(-hourStep * 60 * 60);
39863 $scope.incrementMinutes = function() {
39864 if (!$scope.noIncrementMinutes()) {
39865 addSecondsToSelected(minuteStep * 60);
39869 $scope.decrementMinutes = function() {
39870 if (!$scope.noDecrementMinutes()) {
39871 addSecondsToSelected(-minuteStep * 60);
39875 $scope.incrementSeconds = function() {
39876 if (!$scope.noIncrementSeconds()) {
39877 addSecondsToSelected(secondStep);
39881 $scope.decrementSeconds = function() {
39882 if (!$scope.noDecrementSeconds()) {
39883 addSecondsToSelected(-secondStep);
39887 $scope.toggleMeridian = function() {
39888 var minutes = getMinutesFromTemplate(),
39889 hours = getHoursFromTemplate();
39891 if (!$scope.noToggleMeridian()) {
39892 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39893 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
39895 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
39900 $scope.blur = function() {
39901 ngModelCtrl.$setTouched();
39905 .directive('uibTimepicker', function() {
39907 require: ['uibTimepicker', '?^ngModel'],
39908 controller: 'UibTimepickerController',
39909 controllerAs: 'timepicker',
39912 templateUrl: function(element, attrs) {
39913 return attrs.templateUrl || 'uib/template/timepicker/timepicker.html';
39915 link: function(scope, element, attrs, ctrls) {
39916 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39919 timepickerCtrl.init(ngModelCtrl, element.find('input'));
39925 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
39928 * A helper service that can parse typeahead's syntax (string provided by users)
39929 * Extracted to a separate service for ease of unit testing
39931 .factory('uibTypeaheadParser', ['$parse', function($parse) {
39932 // 00000111000000000000022200000000000000003333333333333330000000000044000
39933 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
39935 parse: function(input) {
39936 var match = input.match(TYPEAHEAD_REGEXP);
39939 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
39940 ' but got "' + input + '".');
39944 itemName: match[3],
39945 source: $parse(match[4]),
39946 viewMapper: $parse(match[2] || match[1]),
39947 modelMapper: $parse(match[1])
39953 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
39954 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
39955 var HOT_KEYS = [9, 13, 27, 38, 40];
39956 var eventDebounceTime = 200;
39957 var modelCtrl, ngModelOptions;
39958 //SUPPORTED ATTRIBUTES (OPTIONS)
39960 //minimal no of characters that needs to be entered before typeahead kicks-in
39961 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
39962 if (!minLength && minLength !== 0) {
39966 //minimal wait time after last character typed before typeahead kicks-in
39967 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
39969 //should it restrict model values to the ones selected from the popup only?
39970 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
39971 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
39972 isEditable = newVal !== false;
39975 //binding to a variable that indicates if matches are being retrieved asynchronously
39976 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
39978 //a callback executed when a match is selected
39979 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
39981 //should it select highlighted popup value when losing focus?
39982 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
39984 //binding to a variable that indicates if there were no results after the query is completed
39985 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
39987 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
39989 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
39991 var appendTo = attrs.typeaheadAppendTo ?
39992 originalScope.$eval(attrs.typeaheadAppendTo) : null;
39994 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
39996 //If input matches an item of the list exactly, select it automatically
39997 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
39999 //binding to a variable that indicates if dropdown is open
40000 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
40002 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
40004 //INTERNAL VARIABLES
40006 //model setter executed upon match selection
40007 var parsedModel = $parse(attrs.ngModel);
40008 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
40009 var $setModelValue = function(scope, newValue) {
40010 if (angular.isFunction(parsedModel(originalScope)) &&
40011 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
40012 return invokeModelSetter(scope, {$$$p: newValue});
40015 return parsedModel.assign(scope, newValue);
40018 //expressions used by typeahead
40019 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
40023 //Used to avoid bug in iOS webview where iOS keyboard does not fire
40024 //mousedown & mouseup events
40028 //create a child scope for the typeahead directive so we are not polluting original scope
40029 //with typeahead-specific data (matches, query etc.)
40030 var scope = originalScope.$new();
40031 var offDestroy = originalScope.$on('$destroy', function() {
40034 scope.$on('$destroy', offDestroy);
40037 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
40039 'aria-autocomplete': 'list',
40040 'aria-expanded': false,
40041 'aria-owns': popupId
40044 var inputsContainer, hintInputElem;
40045 //add read-only input to show hint
40047 inputsContainer = angular.element('<div></div>');
40048 inputsContainer.css('position', 'relative');
40049 element.after(inputsContainer);
40050 hintInputElem = element.clone();
40051 hintInputElem.attr('placeholder', '');
40052 hintInputElem.val('');
40053 hintInputElem.css({
40054 'position': 'absolute',
40057 'border-color': 'transparent',
40058 'box-shadow': 'none',
40060 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
40064 'position': 'relative',
40065 'vertical-align': 'top',
40066 'background-color': 'transparent'
40068 inputsContainer.append(hintInputElem);
40069 hintInputElem.after(element);
40072 //pop-up element used to display matches
40073 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
40076 matches: 'matches',
40077 active: 'activeIdx',
40078 select: 'select(activeIdx, evt)',
40079 'move-in-progress': 'moveInProgress',
40081 position: 'position',
40082 'assign-is-open': 'assignIsOpen(isOpen)',
40083 debounce: 'debounceUpdate'
40085 //custom item template
40086 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
40087 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
40090 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
40091 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
40094 var resetHint = function() {
40096 hintInputElem.val('');
40100 var resetMatches = function() {
40101 scope.matches = [];
40102 scope.activeIdx = -1;
40103 element.attr('aria-expanded', false);
40107 var getMatchId = function(index) {
40108 return popupId + '-option-' + index;
40111 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
40112 // This attribute is added or removed automatically when the `activeIdx` changes.
40113 scope.$watch('activeIdx', function(index) {
40115 element.removeAttr('aria-activedescendant');
40117 element.attr('aria-activedescendant', getMatchId(index));
40121 var inputIsExactMatch = function(inputValue, index) {
40122 if (scope.matches.length > index && inputValue) {
40123 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
40129 var getMatchesAsync = function(inputValue, evt) {
40130 var locals = {$viewValue: inputValue};
40131 isLoadingSetter(originalScope, true);
40132 isNoResultsSetter(originalScope, false);
40133 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
40134 //it might happen that several async queries were in progress if a user were typing fast
40135 //but we are interested only in responses that correspond to the current view value
40136 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
40137 if (onCurrentRequest && hasFocus) {
40138 if (matches && matches.length > 0) {
40139 scope.activeIdx = focusFirst ? 0 : -1;
40140 isNoResultsSetter(originalScope, false);
40141 scope.matches.length = 0;
40144 for (var i = 0; i < matches.length; i++) {
40145 locals[parserResult.itemName] = matches[i];
40146 scope.matches.push({
40148 label: parserResult.viewMapper(scope, locals),
40153 scope.query = inputValue;
40154 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
40155 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
40156 //due to other elements being rendered
40157 recalculatePosition();
40159 element.attr('aria-expanded', true);
40161 //Select the single remaining option if user input matches
40162 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
40163 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40164 $$debounce(function() {
40165 scope.select(0, evt);
40166 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40168 scope.select(0, evt);
40173 var firstLabel = scope.matches[0].label;
40174 if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
40175 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
40178 hintInputElem.val('');
40183 isNoResultsSetter(originalScope, true);
40186 if (onCurrentRequest) {
40187 isLoadingSetter(originalScope, false);
40191 isLoadingSetter(originalScope, false);
40192 isNoResultsSetter(originalScope, true);
40196 // bind events only if appendToBody params exist - performance feature
40197 if (appendToBody) {
40198 angular.element($window).on('resize', fireRecalculating);
40199 $document.find('body').on('scroll', fireRecalculating);
40202 // Declare the debounced function outside recalculating for
40203 // proper debouncing
40204 var debouncedRecalculate = $$debounce(function() {
40205 // if popup is visible
40206 if (scope.matches.length) {
40207 recalculatePosition();
40210 scope.moveInProgress = false;
40211 }, eventDebounceTime);
40213 // Default progress type
40214 scope.moveInProgress = false;
40216 function fireRecalculating() {
40217 if (!scope.moveInProgress) {
40218 scope.moveInProgress = true;
40222 debouncedRecalculate();
40225 // recalculate actual position and set new values to scope
40226 // after digest loop is popup in right position
40227 function recalculatePosition() {
40228 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
40229 scope.position.top += element.prop('offsetHeight');
40232 //we need to propagate user's query so we can higlight matches
40233 scope.query = undefined;
40235 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
40236 var timeoutPromise;
40238 var scheduleSearchWithTimeout = function(inputValue) {
40239 timeoutPromise = $timeout(function() {
40240 getMatchesAsync(inputValue);
40244 var cancelPreviousTimeout = function() {
40245 if (timeoutPromise) {
40246 $timeout.cancel(timeoutPromise);
40252 scope.assignIsOpen = function (isOpen) {
40253 isOpenSetter(originalScope, isOpen);
40256 scope.select = function(activeIdx, evt) {
40257 //called from within the $digest() cycle
40262 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
40263 model = parserResult.modelMapper(originalScope, locals);
40264 $setModelValue(originalScope, model);
40265 modelCtrl.$setValidity('editable', true);
40266 modelCtrl.$setValidity('parse', true);
40268 onSelectCallback(originalScope, {
40271 $label: parserResult.viewMapper(originalScope, locals),
40277 //return focus to the input element if a match was selected via a mouse click event
40278 // use timeout to avoid $rootScope:inprog error
40279 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
40280 $timeout(function() { element[0].focus(); }, 0, false);
40284 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
40285 element.on('keydown', function(evt) {
40286 //typeahead is open and an "interesting" key was pressed
40287 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
40291 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
40292 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
40298 evt.preventDefault();
40300 switch (evt.which) {
40303 scope.$apply(function () {
40304 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40305 $$debounce(function() {
40306 scope.select(scope.activeIdx, evt);
40307 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40309 scope.select(scope.activeIdx, evt);
40314 evt.stopPropagation();
40320 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
40322 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40325 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
40327 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40332 element.bind('focus', function (evt) {
40334 if (minLength === 0 && !modelCtrl.$viewValue) {
40335 $timeout(function() {
40336 getMatchesAsync(modelCtrl.$viewValue, evt);
40341 element.bind('blur', function(evt) {
40342 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
40344 scope.$apply(function() {
40345 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
40346 $$debounce(function() {
40347 scope.select(scope.activeIdx, evt);
40348 }, scope.debounceUpdate.blur);
40350 scope.select(scope.activeIdx, evt);
40354 if (!isEditable && modelCtrl.$error.editable) {
40355 modelCtrl.$viewValue = '';
40362 // Keep reference to click handler to unbind it.
40363 var dismissClickHandler = function(evt) {
40365 // Firefox treats right click as a click on document
40366 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
40368 if (!$rootScope.$$phase) {
40374 $document.on('click', dismissClickHandler);
40376 originalScope.$on('$destroy', function() {
40377 $document.off('click', dismissClickHandler);
40378 if (appendToBody || appendTo) {
40382 if (appendToBody) {
40383 angular.element($window).off('resize', fireRecalculating);
40384 $document.find('body').off('scroll', fireRecalculating);
40386 // Prevent jQuery cache memory leak
40390 inputsContainer.remove();
40394 var $popup = $compile(popUpEl)(scope);
40396 if (appendToBody) {
40397 $document.find('body').append($popup);
40398 } else if (appendTo) {
40399 angular.element(appendTo).eq(0).append($popup);
40401 element.after($popup);
40404 this.init = function(_modelCtrl, _ngModelOptions) {
40405 modelCtrl = _modelCtrl;
40406 ngModelOptions = _ngModelOptions;
40408 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
40410 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
40411 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
40412 modelCtrl.$parsers.unshift(function(inputValue) {
40415 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
40416 if (waitTime > 0) {
40417 cancelPreviousTimeout();
40418 scheduleSearchWithTimeout(inputValue);
40420 getMatchesAsync(inputValue);
40423 isLoadingSetter(originalScope, false);
40424 cancelPreviousTimeout();
40433 // Reset in case user had typed something previously.
40434 modelCtrl.$setValidity('editable', true);
40438 modelCtrl.$setValidity('editable', false);
40442 modelCtrl.$formatters.push(function(modelValue) {
40443 var candidateViewValue, emptyViewValue;
40446 // The validity may be set to false via $parsers (see above) if
40447 // the model is restricted to selected values. If the model
40448 // is set manually it is considered to be valid.
40450 modelCtrl.$setValidity('editable', true);
40453 if (inputFormatter) {
40454 locals.$model = modelValue;
40455 return inputFormatter(originalScope, locals);
40458 //it might happen that we don't have enough info to properly render input value
40459 //we need to check for this situation and simply return model value if we can't apply custom formatting
40460 locals[parserResult.itemName] = modelValue;
40461 candidateViewValue = parserResult.viewMapper(originalScope, locals);
40462 locals[parserResult.itemName] = undefined;
40463 emptyViewValue = parserResult.viewMapper(originalScope, locals);
40465 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
40470 .directive('uibTypeahead', function() {
40472 controller: 'UibTypeaheadController',
40473 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
40474 link: function(originalScope, element, attrs, ctrls) {
40475 ctrls[2].init(ctrls[0], ctrls[1]);
40480 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
40487 moveInProgress: '=',
40493 templateUrl: function(element, attrs) {
40494 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
40496 link: function(scope, element, attrs) {
40497 scope.templateUrl = attrs.templateUrl;
40499 scope.isOpen = function() {
40500 var isDropdownOpen = scope.matches.length > 0;
40501 scope.assignIsOpen({ isOpen: isDropdownOpen });
40502 return isDropdownOpen;
40505 scope.isActive = function(matchIdx) {
40506 return scope.active === matchIdx;
40509 scope.selectActive = function(matchIdx) {
40510 scope.active = matchIdx;
40513 scope.selectMatch = function(activeIdx, evt) {
40514 var debounce = scope.debounce();
40515 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
40516 $$debounce(function() {
40517 scope.select({activeIdx: activeIdx, evt: evt});
40518 }, angular.isNumber(debounce) ? debounce : debounce['default']);
40520 scope.select({activeIdx: activeIdx, evt: evt});
40527 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
40534 link: function(scope, element, attrs) {
40535 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
40536 $templateRequest(tplUrl).then(function(tplContent) {
40537 var tplEl = angular.element(tplContent.trim());
40538 element.replaceWith(tplEl);
40539 $compile(tplEl)(scope);
40545 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
40546 var isSanitizePresent;
40547 isSanitizePresent = $injector.has('$sanitize');
40549 function escapeRegexp(queryToEscape) {
40550 // Regex: capture the whole query string and replace it with the string that will be used to match
40551 // the results, for example if the capture is "a" the result will be \a
40552 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
40555 function containsHtml(matchItem) {
40556 return /<.*>/g.test(matchItem);
40559 return function(matchItem, query) {
40560 if (!isSanitizePresent && containsHtml(matchItem)) {
40561 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
40563 matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
40564 if (!isSanitizePresent) {
40565 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
40571 angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
40572 $templateCache.put("uib/template/accordion/accordion-group.html",
40573 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" +
40574 " <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
40575 " <h4 class=\"panel-title\">\n" +
40576 " <div tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></div>\n" +
40579 " <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
40580 " <div class=\"panel-body\" ng-transclude></div>\n" +
40586 angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
40587 $templateCache.put("uib/template/accordion/accordion.html",
40588 "<div class=\"panel-group\" ng-transclude></div>");
40591 angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
40592 $templateCache.put("uib/template/alert/alert.html",
40593 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
40594 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
40595 " <span aria-hidden=\"true\">×</span>\n" +
40596 " <span class=\"sr-only\">Close</span>\n" +
40598 " <div ng-transclude></div>\n" +
40603 angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
40604 $templateCache.put("uib/template/carousel/carousel.html",
40605 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
40606 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
40607 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\">\n" +
40608 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
40609 " <span class=\"sr-only\">previous</span>\n" +
40611 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\">\n" +
40612 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
40613 " <span class=\"sr-only\">next</span>\n" +
40615 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
40616 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
40617 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
40623 angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
40624 $templateCache.put("uib/template/carousel/slide.html",
40625 "<div ng-class=\"{\n" +
40626 " 'active': active\n" +
40627 " }\" class=\"item text-center\" ng-transclude></div>\n" +
40631 angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
40632 $templateCache.put("uib/template/datepicker/datepicker.html",
40633 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
40634 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
40635 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
40636 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
40640 angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
40641 $templateCache.put("uib/template/datepicker/day.html",
40642 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40645 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
40646 " <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
40647 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
40650 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
40651 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
40655 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
40656 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
40657 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
40658 " id=\"{{::dt.uid}}\"\n" +
40659 " ng-class=\"::dt.customClass\">\n" +
40660 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default btn-sm\"\n" +
40661 " uib-is-class=\"\n" +
40662 " 'btn-info' for selectedDt,\n" +
40663 " 'active' for activeDt\n" +
40665 " ng-click=\"select(dt.date)\"\n" +
40666 " ng-disabled=\"::dt.disabled\"\n" +
40667 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40675 angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
40676 $templateCache.put("uib/template/datepicker/month.html",
40677 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40680 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
40681 " <th><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
40682 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
40686 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
40687 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
40688 " id=\"{{::dt.uid}}\"\n" +
40689 " ng-class=\"::dt.customClass\">\n" +
40690 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40691 " uib-is-class=\"\n" +
40692 " 'btn-info' for selectedDt,\n" +
40693 " 'active' for activeDt\n" +
40695 " ng-click=\"select(dt.date)\"\n" +
40696 " ng-disabled=\"::dt.disabled\"\n" +
40697 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40705 angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
40706 $templateCache.put("uib/template/datepicker/popup.html",
40707 "<ul class=\"uib-datepicker-popup dropdown-menu\" dropdown-nested ng-if=\"isOpen\" style=\"display: block\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
40708 " <li ng-transclude></li>\n" +
40709 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\" class=\"uib-button-bar\">\n" +
40710 " <span class=\"btn-group pull-left\">\n" +
40711 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
40712 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
40714 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
40720 angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
40721 $templateCache.put("uib/template/datepicker/year.html",
40722 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40725 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
40726 " <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
40727 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
40731 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
40732 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
40733 " id=\"{{::dt.uid}}\"\n" +
40734 " ng-class=\"::dt.customClass\">\n" +
40735 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40736 " uib-is-class=\"\n" +
40737 " 'btn-info' for selectedDt,\n" +
40738 " 'active' for activeDt\n" +
40740 " ng-click=\"select(dt.date)\"\n" +
40741 " ng-disabled=\"::dt.disabled\"\n" +
40742 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40750 angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
40751 $templateCache.put("uib/template/modal/backdrop.html",
40752 "<div class=\"modal-backdrop\"\n" +
40753 " uib-modal-animation-class=\"fade\"\n" +
40754 " modal-in-class=\"in\"\n" +
40755 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
40760 angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
40761 $templateCache.put("uib/template/modal/window.html",
40762 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
40763 " uib-modal-animation-class=\"fade\"\n" +
40764 " modal-in-class=\"in\"\n" +
40765 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
40766 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
40771 angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
40772 $templateCache.put("uib/template/pager/pager.html",
40773 "<ul class=\"pager\">\n" +
40774 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40775 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40780 angular.module("uib/template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
40781 $templateCache.put("uib/template/pagination/pager.html",
40782 "<ul class=\"pager\">\n" +
40783 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40784 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40789 angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
40790 $templateCache.put("uib/template/pagination/pagination.html",
40791 "<ul class=\"pagination\">\n" +
40792 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
40793 " <li ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40794 " <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\">{{page.text}}</a></li>\n" +
40795 " <li ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40796 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
40801 angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
40802 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
40803 "<div class=\"tooltip\"\n" +
40804 " tooltip-animation-class=\"fade\"\n" +
40805 " uib-tooltip-classes\n" +
40806 " ng-class=\"{ in: isOpen() }\">\n" +
40807 " <div class=\"tooltip-arrow\"></div>\n" +
40808 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
40813 angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
40814 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
40815 "<div class=\"tooltip\"\n" +
40816 " tooltip-animation-class=\"fade\"\n" +
40817 " uib-tooltip-classes\n" +
40818 " ng-class=\"{ in: isOpen() }\">\n" +
40819 " <div class=\"tooltip-arrow\"></div>\n" +
40820 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
40825 angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
40826 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
40827 "<div class=\"tooltip\"\n" +
40828 " tooltip-animation-class=\"fade\"\n" +
40829 " uib-tooltip-classes\n" +
40830 " ng-class=\"{ in: isOpen() }\">\n" +
40831 " <div class=\"tooltip-arrow\"></div>\n" +
40832 " <div class=\"tooltip-inner\"\n" +
40833 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40834 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40839 angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
40840 $templateCache.put("uib/template/popover/popover-html.html",
40841 "<div class=\"popover\"\n" +
40842 " tooltip-animation-class=\"fade\"\n" +
40843 " uib-tooltip-classes\n" +
40844 " ng-class=\"{ in: isOpen() }\">\n" +
40845 " <div class=\"arrow\"></div>\n" +
40847 " <div class=\"popover-inner\">\n" +
40848 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40849 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
40855 angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
40856 $templateCache.put("uib/template/popover/popover-template.html",
40857 "<div class=\"popover\"\n" +
40858 " tooltip-animation-class=\"fade\"\n" +
40859 " uib-tooltip-classes\n" +
40860 " ng-class=\"{ in: isOpen() }\">\n" +
40861 " <div class=\"arrow\"></div>\n" +
40863 " <div class=\"popover-inner\">\n" +
40864 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40865 " <div class=\"popover-content\"\n" +
40866 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40867 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40873 angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
40874 $templateCache.put("uib/template/popover/popover.html",
40875 "<div class=\"popover\"\n" +
40876 " tooltip-animation-class=\"fade\"\n" +
40877 " uib-tooltip-classes\n" +
40878 " ng-class=\"{ in: isOpen() }\">\n" +
40879 " <div class=\"arrow\"></div>\n" +
40881 " <div class=\"popover-inner\">\n" +
40882 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40883 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
40889 angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
40890 $templateCache.put("uib/template/progressbar/bar.html",
40891 "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" +
40895 angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
40896 $templateCache.put("uib/template/progressbar/progress.html",
40897 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
40900 angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
40901 $templateCache.put("uib/template/progressbar/progressbar.html",
40902 "<div class=\"progress\">\n" +
40903 " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" +
40908 angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
40909 $templateCache.put("uib/template/rating/rating.html",
40910 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
40911 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
40912 " <i ng-repeat-end ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\" ng-attr-title=\"{{r.title}}\" aria-valuetext=\"{{r.title}}\"></i>\n" +
40917 angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
40918 $templateCache.put("uib/template/tabs/tab.html",
40919 "<li ng-class=\"{active: active, disabled: disabled}\" class=\"uib-tab\">\n" +
40920 " <div ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</div>\n" +
40925 angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
40926 $templateCache.put("uib/template/tabs/tabset.html",
40928 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
40929 " <div class=\"tab-content\">\n" +
40930 " <div class=\"tab-pane\" \n" +
40931 " ng-repeat=\"tab in tabs\" \n" +
40932 " ng-class=\"{active: tab.active}\"\n" +
40933 " uib-tab-content-transclude=\"tab\">\n" +
40940 angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
40941 $templateCache.put("uib/template/timepicker/timepicker.html",
40942 "<table class=\"uib-timepicker\">\n" +
40944 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40945 " <td class=\"uib-increment hours\"><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
40946 " <td> </td>\n" +
40947 " <td class=\"uib-increment minutes\"><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
40948 " <td ng-show=\"showSeconds\"> </td>\n" +
40949 " <td ng-show=\"showSeconds\" class=\"uib-increment seconds\"><a ng-click=\"incrementSeconds()\" ng-class=\"{disabled: noIncrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementSeconds()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
40950 " <td ng-show=\"showMeridian\"></td>\n" +
40953 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
40954 " <input style=\"width:50px;\" type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"disabled\" ng-blur=\"blur()\">\n" +
40956 " <td class=\"uib-separator\">:</td>\n" +
40957 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
40958 " <input style=\"width:50px;\" type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"disabled\" ng-blur=\"blur()\">\n" +
40960 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
40961 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
40962 " <input style=\"width:50px;\" type=\"text\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"disabled\" ng-blur=\"blur()\">\n" +
40964 " <td ng-show=\"showMeridian\" class=\"uib-time am-pm\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n" +
40966 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40967 " <td class=\"uib-decrement hours\"><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
40968 " <td> </td>\n" +
40969 " <td class=\"uib-decrement minutes\"><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
40970 " <td ng-show=\"showSeconds\"> </td>\n" +
40971 " <td ng-show=\"showSeconds\" class=\"uib-decrement seconds\"><a ng-click=\"decrementSeconds()\" ng-class=\"{disabled: noDecrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementSeconds()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
40972 " <td ng-show=\"showMeridian\"></td>\n" +
40979 angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
40980 $templateCache.put("uib/template/typeahead/typeahead-match.html",
40981 "<a href tabindex=\"-1\" ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></a>\n" +
40985 angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
40986 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
40987 "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
40988 " <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index, $event)\" role=\"option\" id=\"{{::match.id}}\">\n" +
40989 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
40994 angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); })
40998 /***/ function(module, exports) {
41003 (function (declares) {
41004 var CommandInfo = (function () {
41005 function CommandInfo(name) {
41008 return CommandInfo;
41010 declares.CommandInfo = CommandInfo;
41011 })(declares = app.declares || (app.declares = {}));
41012 })(app || (app = {}));
41016 (function (services) {
41017 var APIEndPoint = (function () {
41018 function APIEndPoint($resource) {
41019 this.$resource = $resource;
41021 APIEndPoint.prototype.resource = function () {
41026 var apiUrl = '/api/optionControlFile/dcdFilePrint';
41027 return this.$resource(apiUrl, {}, { getOption: getOption });
41029 APIEndPoint.prototype.getOptionControlFile = function () {
41030 return this.resource().query();
41032 return APIEndPoint;
41034 services.APIEndPoint = APIEndPoint;
41035 })(services = app.services || (app.services = {}));
41036 })(app || (app = {}));
41040 (function (services) {
41041 var MyModal = (function () {
41042 function MyModal($uibModal) {
41043 this.$uibModal = $uibModal;
41044 this.modalOption = {
41051 MyModal.prototype.open = function (modalName) {
41052 if (modalName === 'SelectCommand') {
41053 this.modalOption.templateUrl = 'templates/select-command.html';
41054 this.modalOption.size = 'lg';
41056 return this.$uibModal.open(this.modalOption);
41060 services.MyModal = MyModal;
41061 })(services = app.services || (app.services = {}));
41062 })(app || (app = {}));
41066 (function (directives) {
41067 var Command = (function () {
41068 function Command() {
41069 this.restrict = 'E';
41070 this.replace = true;
41072 this.controller = 'commandController';
41073 this.controllerAs = 'ctrl';
41074 this.bindToController = {
41080 this.templateUrl = 'templates/command.html';
41082 Command.Factory = function () {
41083 var directive = function () {
41084 return new Command();
41086 directive.$inject = [];
41091 directives.Command = Command;
41092 var CommandController = (function () {
41093 function CommandController(APIEndPoint, $scope) {
41094 this.APIEndPoint = APIEndPoint;
41095 this.$scope = $scope;
41096 var controller = this;
41098 .getOptionControlFile()
41100 .then(function (result) {
41101 controller.options = result;
41103 this.files = ['a.file', 'b.file', 'c.file'];
41104 this.heading = "[" + this.index + "]: dcdFilePring";
41105 this.isOpen = true;
41106 this.$scope.$on('close', function () {
41107 controller.isOpen = false;
41110 CommandController.prototype.submit = function () {
41112 angular.forEach(this.options, function (option) {
41114 angular.forEach(option.arg, function (arg) {
41116 inputs.push(arg.input);
41119 if (inputs.length > 0) {
41120 params[option.option] = inputs;
41123 console.log(params);
41125 CommandController.prototype.removeMySelf = function (index) {
41126 this.remove()(index, this.list);
41128 CommandController.$inject = ['APIEndPoint', '$scope'];
41129 return CommandController;
41131 directives.CommandController = CommandController;
41132 })(directives = app.directives || (app.directives = {}));
41133 })(app || (app = {}));
41137 (function (directives) {
41138 var HeaderMenu = (function () {
41139 function HeaderMenu() {
41140 this.restrict = 'E';
41141 this.replace = true;
41142 this.templateUrl = 'templates/header-menu.html';
41144 HeaderMenu.Factory = function () {
41145 var directive = function () {
41146 return new HeaderMenu();
41152 directives.HeaderMenu = HeaderMenu;
41153 })(directives = app.directives || (app.directives = {}));
41154 })(app || (app = {}));
41158 (function (directives) {
41159 var Option = (function () {
41160 function Option() {
41161 this.restrict = 'E';
41162 this.replace = true;
41163 this.controller = 'optionController';
41164 this.bindToController = {
41169 this.templateUrl = 'templates/option.html';
41170 this.controllerAs = 'ctrl';
41172 Option.Factory = function () {
41173 var directive = function () {
41174 return new Option();
41176 directive.$inject = [];
41181 directives.Option = Option;
41182 var OptionController = (function () {
41183 function OptionController() {
41184 var controller = this;
41185 angular.forEach(controller.info.arg, function (arg) {
41186 if (arg.initialValue) {
41187 if (arg.formType === 'number') {
41188 arg.input = parseInt(arg.initialValue);
41191 arg.input = arg.initialValue;
41196 OptionController.$inject = [];
41197 return OptionController;
41199 directives.OptionController = OptionController;
41200 })(directives = app.directives || (app.directives = {}));
41201 })(app || (app = {}));
41205 (function (controllers) {
41206 var Execution = (function () {
41207 function Execution(MyModal, $scope) {
41208 this.MyModal = MyModal;
41209 this.$scope = $scope;
41210 this.commandInfoList = [];
41213 Execution.prototype.add = function () {
41214 this.$scope.$broadcast('close');
41215 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
41217 Execution.prototype.open = function () {
41218 var result = this.MyModal.open('SelectCommand');
41219 console.log(result);
41221 Execution.prototype.remove = function (index, list) {
41222 list.splice(index, 1);
41224 Execution.prototype.close = function () {
41225 console.log("close");
41227 Execution.$inject = ['MyModal', '$scope'];
41230 controllers.Execution = Execution;
41231 })(controllers = app.controllers || (app.controllers = {}));
41232 })(app || (app = {}));
41236 (function (controllers) {
41237 var Files = (function () {
41238 function Files($scope) {
41239 this.page = "ManageFiles";
41241 Files.$inject = ['$scope'];
41244 controllers.Files = Files;
41245 })(controllers = app.controllers || (app.controllers = {}));
41246 })(app || (app = {}));
41250 (function (controllers) {
41251 var History = (function () {
41252 function History($scope) {
41253 this.page = "History";
41255 History.$inject = ['$scope'];
41258 controllers.History = History;
41259 })(controllers = app.controllers || (app.controllers = {}));
41260 })(app || (app = {}));
41264 var appName = 'zephyr';
41265 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41266 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41267 $urlRouterProvider.otherwise('/execution');
41268 $locationProvider.html5Mode({
41273 .state('execution', {
41275 templateUrl: 'templates/execution.html',
41276 controller: 'executionController',
41281 templateUrl: 'templates/files.html',
41282 controller: 'filesController',
41285 .state('history', {
41287 templateUrl: 'templates/history.html',
41288 controller: 'historyController',
41292 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41293 app.zephyr.service('MyModal', app.services.MyModal);
41294 app.zephyr.controller('executionController', app.controllers.Execution);
41295 app.zephyr.controller('filesController', app.controllers.Files);
41296 app.zephyr.controller('historyController', app.controllers.History);
41297 app.zephyr.controller('commandController', app.directives.CommandController);
41298 app.zephyr.controller('optionController', app.directives.OptionController);
41299 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41300 app.zephyr.directive('command', app.directives.Command.Factory());
41301 app.zephyr.directive('option', app.directives.Option.Factory());
41302 })(app || (app = {}));
41307 /***/ function(module, exports) {
41312 (function (declares) {
41313 var CommandInfo = (function () {
41314 function CommandInfo(name) {
41317 return CommandInfo;
41319 declares.CommandInfo = CommandInfo;
41320 })(declares = app.declares || (app.declares = {}));
41321 })(app || (app = {}));
41325 (function (services) {
41326 var APIEndPoint = (function () {
41327 function APIEndPoint($resource) {
41328 this.$resource = $resource;
41330 APIEndPoint.prototype.resource = function () {
41335 var apiUrl = '/api/optionControlFile/dcdFilePrint';
41336 return this.$resource(apiUrl, {}, { getOption: getOption });
41338 APIEndPoint.prototype.getOptionControlFile = function () {
41339 return this.resource().query();
41341 return APIEndPoint;
41343 services.APIEndPoint = APIEndPoint;
41344 })(services = app.services || (app.services = {}));
41345 })(app || (app = {}));
41349 (function (services) {
41350 var MyModal = (function () {
41351 function MyModal($uibModal) {
41352 this.$uibModal = $uibModal;
41353 this.modalOption = {
41360 MyModal.prototype.open = function (modalName) {
41361 if (modalName === 'SelectCommand') {
41362 this.modalOption.templateUrl = 'templates/select-command.html';
41363 this.modalOption.size = 'lg';
41365 return this.$uibModal.open(this.modalOption);
41369 services.MyModal = MyModal;
41370 })(services = app.services || (app.services = {}));
41371 })(app || (app = {}));
41375 (function (directives) {
41376 var Command = (function () {
41377 function Command() {
41378 this.restrict = 'E';
41379 this.replace = true;
41381 this.controller = 'commandController';
41382 this.controllerAs = 'ctrl';
41383 this.bindToController = {
41389 this.templateUrl = 'templates/command.html';
41391 Command.Factory = function () {
41392 var directive = function () {
41393 return new Command();
41395 directive.$inject = [];
41400 directives.Command = Command;
41401 var CommandController = (function () {
41402 function CommandController(APIEndPoint, $scope) {
41403 this.APIEndPoint = APIEndPoint;
41404 this.$scope = $scope;
41405 var controller = this;
41407 .getOptionControlFile()
41409 .then(function (result) {
41410 controller.options = result;
41412 this.files = ['a.file', 'b.file', 'c.file'];
41413 this.heading = "[" + this.index + "]: dcdFilePring";
41414 this.isOpen = true;
41415 this.$scope.$on('close', function () {
41416 controller.isOpen = false;
41419 CommandController.prototype.submit = function () {
41421 angular.forEach(this.options, function (option) {
41423 angular.forEach(option.arg, function (arg) {
41425 inputs.push(arg.input);
41428 if (inputs.length > 0) {
41429 params[option.option] = inputs;
41432 console.log(params);
41434 CommandController.prototype.removeMySelf = function (index) {
41435 this.remove()(index, this.list);
41437 CommandController.$inject = ['APIEndPoint', '$scope'];
41438 return CommandController;
41440 directives.CommandController = CommandController;
41441 })(directives = app.directives || (app.directives = {}));
41442 })(app || (app = {}));
41446 (function (directives) {
41447 var HeaderMenu = (function () {
41448 function HeaderMenu() {
41449 this.restrict = 'E';
41450 this.replace = true;
41451 this.templateUrl = 'templates/header-menu.html';
41453 HeaderMenu.Factory = function () {
41454 var directive = function () {
41455 return new HeaderMenu();
41461 directives.HeaderMenu = HeaderMenu;
41462 })(directives = app.directives || (app.directives = {}));
41463 })(app || (app = {}));
41467 (function (directives) {
41468 var Option = (function () {
41469 function Option() {
41470 this.restrict = 'E';
41471 this.replace = true;
41472 this.controller = 'optionController';
41473 this.bindToController = {
41478 this.templateUrl = 'templates/option.html';
41479 this.controllerAs = 'ctrl';
41481 Option.Factory = function () {
41482 var directive = function () {
41483 return new Option();
41485 directive.$inject = [];
41490 directives.Option = Option;
41491 var OptionController = (function () {
41492 function OptionController() {
41493 var controller = this;
41494 angular.forEach(controller.info.arg, function (arg) {
41495 if (arg.initialValue) {
41496 if (arg.formType === 'number') {
41497 arg.input = parseInt(arg.initialValue);
41500 arg.input = arg.initialValue;
41505 OptionController.$inject = [];
41506 return OptionController;
41508 directives.OptionController = OptionController;
41509 })(directives = app.directives || (app.directives = {}));
41510 })(app || (app = {}));
41514 (function (controllers) {
41515 var Execution = (function () {
41516 function Execution(MyModal, $scope) {
41517 this.MyModal = MyModal;
41518 this.$scope = $scope;
41519 this.commandInfoList = [];
41522 Execution.prototype.add = function () {
41523 this.$scope.$broadcast('close');
41524 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
41526 Execution.prototype.open = function () {
41527 var result = this.MyModal.open('SelectCommand');
41528 console.log(result);
41530 Execution.prototype.remove = function (index, list) {
41531 list.splice(index, 1);
41533 Execution.prototype.close = function () {
41534 console.log("close");
41536 Execution.$inject = ['MyModal', '$scope'];
41539 controllers.Execution = Execution;
41540 })(controllers = app.controllers || (app.controllers = {}));
41541 })(app || (app = {}));
41545 (function (controllers) {
41546 var Files = (function () {
41547 function Files($scope) {
41548 this.page = "ManageFiles";
41550 Files.$inject = ['$scope'];
41553 controllers.Files = Files;
41554 })(controllers = app.controllers || (app.controllers = {}));
41555 })(app || (app = {}));
41559 (function (controllers) {
41560 var History = (function () {
41561 function History($scope) {
41562 this.page = "History";
41564 History.$inject = ['$scope'];
41567 controllers.History = History;
41568 })(controllers = app.controllers || (app.controllers = {}));
41569 })(app || (app = {}));
41573 var appName = 'zephyr';
41574 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41575 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41576 $urlRouterProvider.otherwise('/execution');
41577 $locationProvider.html5Mode({
41582 .state('execution', {
41584 templateUrl: 'templates/execution.html',
41585 controller: 'executionController',
41590 templateUrl: 'templates/files.html',
41591 controller: 'filesController',
41594 .state('history', {
41596 templateUrl: 'templates/history.html',
41597 controller: 'historyController',
41601 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41602 app.zephyr.service('MyModal', app.services.MyModal);
41603 app.zephyr.controller('executionController', app.controllers.Execution);
41604 app.zephyr.controller('filesController', app.controllers.Files);
41605 app.zephyr.controller('historyController', app.controllers.History);
41606 app.zephyr.controller('commandController', app.directives.CommandController);
41607 app.zephyr.controller('optionController', app.directives.OptionController);
41608 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41609 app.zephyr.directive('command', app.directives.Command.Factory());
41610 app.zephyr.directive('option', app.directives.Option.Factory());
41611 })(app || (app = {}));
41616 /***/ function(module, exports) {
41621 (function (declares) {
41622 var CommandInfo = (function () {
41623 function CommandInfo(name) {
41626 return CommandInfo;
41628 declares.CommandInfo = CommandInfo;
41629 })(declares = app.declares || (app.declares = {}));
41630 })(app || (app = {}));
41634 (function (services) {
41635 var APIEndPoint = (function () {
41636 function APIEndPoint($resource) {
41637 this.$resource = $resource;
41639 APIEndPoint.prototype.resource = function () {
41644 var apiUrl = '/api/optionControlFile/dcdFilePrint';
41645 return this.$resource(apiUrl, {}, { getOption: getOption });
41647 APIEndPoint.prototype.getOptionControlFile = function () {
41648 return this.resource().query();
41650 return APIEndPoint;
41652 services.APIEndPoint = APIEndPoint;
41653 })(services = app.services || (app.services = {}));
41654 })(app || (app = {}));
41658 (function (services) {
41659 var MyModal = (function () {
41660 function MyModal($uibModal) {
41661 this.$uibModal = $uibModal;
41662 this.modalOption = {
41669 MyModal.prototype.open = function (modalName) {
41670 if (modalName === 'SelectCommand') {
41671 this.modalOption.templateUrl = 'templates/select-command.html';
41672 this.modalOption.size = 'lg';
41674 return this.$uibModal.open(this.modalOption);
41678 services.MyModal = MyModal;
41679 })(services = app.services || (app.services = {}));
41680 })(app || (app = {}));
41684 (function (directives) {
41685 var Command = (function () {
41686 function Command() {
41687 this.restrict = 'E';
41688 this.replace = true;
41690 this.controller = 'commandController';
41691 this.controllerAs = 'ctrl';
41692 this.bindToController = {
41698 this.templateUrl = 'templates/command.html';
41700 Command.Factory = function () {
41701 var directive = function () {
41702 return new Command();
41704 directive.$inject = [];
41709 directives.Command = Command;
41710 var CommandController = (function () {
41711 function CommandController(APIEndPoint, $scope) {
41712 this.APIEndPoint = APIEndPoint;
41713 this.$scope = $scope;
41714 var controller = this;
41716 .getOptionControlFile()
41718 .then(function (result) {
41719 controller.options = result;
41721 this.files = ['a.file', 'b.file', 'c.file'];
41722 this.heading = "[" + this.index + "]: dcdFilePring";
41723 this.isOpen = true;
41724 this.$scope.$on('close', function () {
41725 controller.isOpen = false;
41728 CommandController.prototype.submit = function () {
41730 angular.forEach(this.options, function (option) {
41732 angular.forEach(option.arg, function (arg) {
41734 inputs.push(arg.input);
41737 if (inputs.length > 0) {
41738 params[option.option] = inputs;
41741 console.log(params);
41743 CommandController.prototype.removeMySelf = function (index) {
41744 this.remove()(index, this.list);
41746 CommandController.$inject = ['APIEndPoint', '$scope'];
41747 return CommandController;
41749 directives.CommandController = CommandController;
41750 })(directives = app.directives || (app.directives = {}));
41751 })(app || (app = {}));
41755 (function (directives) {
41756 var HeaderMenu = (function () {
41757 function HeaderMenu() {
41758 this.restrict = 'E';
41759 this.replace = true;
41760 this.templateUrl = 'templates/header-menu.html';
41762 HeaderMenu.Factory = function () {
41763 var directive = function () {
41764 return new HeaderMenu();
41770 directives.HeaderMenu = HeaderMenu;
41771 })(directives = app.directives || (app.directives = {}));
41772 })(app || (app = {}));
41776 (function (directives) {
41777 var Option = (function () {
41778 function Option() {
41779 this.restrict = 'E';
41780 this.replace = true;
41781 this.controller = 'optionController';
41782 this.bindToController = {
41787 this.templateUrl = 'templates/option.html';
41788 this.controllerAs = 'ctrl';
41790 Option.Factory = function () {
41791 var directive = function () {
41792 return new Option();
41794 directive.$inject = [];
41799 directives.Option = Option;
41800 var OptionController = (function () {
41801 function OptionController() {
41802 var controller = this;
41803 angular.forEach(controller.info.arg, function (arg) {
41804 if (arg.initialValue) {
41805 if (arg.formType === 'number') {
41806 arg.input = parseInt(arg.initialValue);
41809 arg.input = arg.initialValue;
41814 OptionController.$inject = [];
41815 return OptionController;
41817 directives.OptionController = OptionController;
41818 })(directives = app.directives || (app.directives = {}));
41819 })(app || (app = {}));
41823 (function (controllers) {
41824 var Execution = (function () {
41825 function Execution(MyModal, $scope) {
41826 this.MyModal = MyModal;
41827 this.$scope = $scope;
41828 this.commandInfoList = [];
41831 Execution.prototype.add = function () {
41832 this.$scope.$broadcast('close');
41833 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
41835 Execution.prototype.open = function () {
41836 var result = this.MyModal.open('SelectCommand');
41837 console.log(result);
41839 Execution.prototype.remove = function (index, list) {
41840 list.splice(index, 1);
41842 Execution.prototype.close = function () {
41843 console.log("close");
41845 Execution.$inject = ['MyModal', '$scope'];
41848 controllers.Execution = Execution;
41849 })(controllers = app.controllers || (app.controllers = {}));
41850 })(app || (app = {}));
41854 (function (controllers) {
41855 var Files = (function () {
41856 function Files($scope) {
41857 this.page = "ManageFiles";
41859 Files.$inject = ['$scope'];
41862 controllers.Files = Files;
41863 })(controllers = app.controllers || (app.controllers = {}));
41864 })(app || (app = {}));
41868 (function (controllers) {
41869 var History = (function () {
41870 function History($scope) {
41871 this.page = "History";
41873 History.$inject = ['$scope'];
41876 controllers.History = History;
41877 })(controllers = app.controllers || (app.controllers = {}));
41878 })(app || (app = {}));
41882 var appName = 'zephyr';
41883 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41884 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41885 $urlRouterProvider.otherwise('/execution');
41886 $locationProvider.html5Mode({
41891 .state('execution', {
41893 templateUrl: 'templates/execution.html',
41894 controller: 'executionController',
41899 templateUrl: 'templates/files.html',
41900 controller: 'filesController',
41903 .state('history', {
41905 templateUrl: 'templates/history.html',
41906 controller: 'historyController',
41910 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41911 app.zephyr.service('MyModal', app.services.MyModal);
41912 app.zephyr.controller('executionController', app.controllers.Execution);
41913 app.zephyr.controller('filesController', app.controllers.Files);
41914 app.zephyr.controller('historyController', app.controllers.History);
41915 app.zephyr.controller('commandController', app.directives.CommandController);
41916 app.zephyr.controller('optionController', app.directives.OptionController);
41917 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41918 app.zephyr.directive('command', app.directives.Command.Factory());
41919 app.zephyr.directive('option', app.directives.Option.Factory());
41920 })(app || (app = {}));
41925 /***/ function(module, exports) {
41930 (function (declares) {
41931 var CommandInfo = (function () {
41932 function CommandInfo(name) {
41935 return CommandInfo;
41937 declares.CommandInfo = CommandInfo;
41938 })(declares = app.declares || (app.declares = {}));
41939 })(app || (app = {}));
41943 (function (services) {
41944 var APIEndPoint = (function () {
41945 function APIEndPoint($resource) {
41946 this.$resource = $resource;
41948 APIEndPoint.prototype.resource = function () {
41953 var apiUrl = '/api/optionControlFile/dcdFilePrint';
41954 return this.$resource(apiUrl, {}, { getOption: getOption });
41956 APIEndPoint.prototype.getOptionControlFile = function () {
41957 return this.resource().query();
41959 return APIEndPoint;
41961 services.APIEndPoint = APIEndPoint;
41962 })(services = app.services || (app.services = {}));
41963 })(app || (app = {}));
41967 (function (services) {
41968 var MyModal = (function () {
41969 function MyModal($uibModal) {
41970 this.$uibModal = $uibModal;
41971 this.modalOption = {
41978 MyModal.prototype.open = function (modalName) {
41979 if (modalName === 'SelectCommand') {
41980 this.modalOption.templateUrl = 'templates/select-command.html';
41981 this.modalOption.size = 'lg';
41983 return this.$uibModal.open(this.modalOption);
41987 services.MyModal = MyModal;
41988 })(services = app.services || (app.services = {}));
41989 })(app || (app = {}));
41993 (function (directives) {
41994 var Command = (function () {
41995 function Command() {
41996 this.restrict = 'E';
41997 this.replace = true;
41999 this.controller = 'commandController';
42000 this.controllerAs = 'ctrl';
42001 this.bindToController = {
42007 this.templateUrl = 'templates/command.html';
42009 Command.Factory = function () {
42010 var directive = function () {
42011 return new Command();
42013 directive.$inject = [];
42018 directives.Command = Command;
42019 var CommandController = (function () {
42020 function CommandController(APIEndPoint, $scope) {
42021 this.APIEndPoint = APIEndPoint;
42022 this.$scope = $scope;
42023 var controller = this;
42025 .getOptionControlFile()
42027 .then(function (result) {
42028 controller.options = result;
42030 this.files = ['a.file', 'b.file', 'c.file'];
42031 this.heading = "[" + this.index + "]: dcdFilePring";
42032 this.isOpen = true;
42033 this.$scope.$on('close', function () {
42034 controller.isOpen = false;
42037 CommandController.prototype.submit = function () {
42039 angular.forEach(this.options, function (option) {
42041 angular.forEach(option.arg, function (arg) {
42043 inputs.push(arg.input);
42046 if (inputs.length > 0) {
42047 params[option.option] = inputs;
42050 console.log(params);
42052 CommandController.prototype.removeMySelf = function (index) {
42053 this.remove()(index, this.list);
42055 CommandController.$inject = ['APIEndPoint', '$scope'];
42056 return CommandController;
42058 directives.CommandController = CommandController;
42059 })(directives = app.directives || (app.directives = {}));
42060 })(app || (app = {}));
42064 (function (directives) {
42065 var HeaderMenu = (function () {
42066 function HeaderMenu() {
42067 this.restrict = 'E';
42068 this.replace = true;
42069 this.templateUrl = 'templates/header-menu.html';
42071 HeaderMenu.Factory = function () {
42072 var directive = function () {
42073 return new HeaderMenu();
42079 directives.HeaderMenu = HeaderMenu;
42080 })(directives = app.directives || (app.directives = {}));
42081 })(app || (app = {}));
42085 (function (directives) {
42086 var Option = (function () {
42087 function Option() {
42088 this.restrict = 'E';
42089 this.replace = true;
42090 this.controller = 'optionController';
42091 this.bindToController = {
42096 this.templateUrl = 'templates/option.html';
42097 this.controllerAs = 'ctrl';
42099 Option.Factory = function () {
42100 var directive = function () {
42101 return new Option();
42103 directive.$inject = [];
42108 directives.Option = Option;
42109 var OptionController = (function () {
42110 function OptionController() {
42111 var controller = this;
42112 angular.forEach(controller.info.arg, function (arg) {
42113 if (arg.initialValue) {
42114 if (arg.formType === 'number') {
42115 arg.input = parseInt(arg.initialValue);
42118 arg.input = arg.initialValue;
42123 OptionController.$inject = [];
42124 return OptionController;
42126 directives.OptionController = OptionController;
42127 })(directives = app.directives || (app.directives = {}));
42128 })(app || (app = {}));
42132 (function (controllers) {
42133 var Execution = (function () {
42134 function Execution(MyModal, $scope) {
42135 this.MyModal = MyModal;
42136 this.$scope = $scope;
42137 this.commandInfoList = [];
42140 Execution.prototype.add = function () {
42141 this.$scope.$broadcast('close');
42142 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
42144 Execution.prototype.open = function () {
42145 var result = this.MyModal.open('SelectCommand');
42146 console.log(result);
42148 Execution.prototype.remove = function (index, list) {
42149 list.splice(index, 1);
42151 Execution.prototype.close = function () {
42152 console.log("close");
42154 Execution.$inject = ['MyModal', '$scope'];
42157 controllers.Execution = Execution;
42158 })(controllers = app.controllers || (app.controllers = {}));
42159 })(app || (app = {}));
42163 (function (controllers) {
42164 var Files = (function () {
42165 function Files($scope) {
42166 this.page = "ManageFiles";
42168 Files.$inject = ['$scope'];
42171 controllers.Files = Files;
42172 })(controllers = app.controllers || (app.controllers = {}));
42173 })(app || (app = {}));
42177 (function (controllers) {
42178 var History = (function () {
42179 function History($scope) {
42180 this.page = "History";
42182 History.$inject = ['$scope'];
42185 controllers.History = History;
42186 })(controllers = app.controllers || (app.controllers = {}));
42187 })(app || (app = {}));
42191 var appName = 'zephyr';
42192 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42193 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42194 $urlRouterProvider.otherwise('/execution');
42195 $locationProvider.html5Mode({
42200 .state('execution', {
42202 templateUrl: 'templates/execution.html',
42203 controller: 'executionController',
42208 templateUrl: 'templates/files.html',
42209 controller: 'filesController',
42212 .state('history', {
42214 templateUrl: 'templates/history.html',
42215 controller: 'historyController',
42219 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42220 app.zephyr.service('MyModal', app.services.MyModal);
42221 app.zephyr.controller('executionController', app.controllers.Execution);
42222 app.zephyr.controller('filesController', app.controllers.Files);
42223 app.zephyr.controller('historyController', app.controllers.History);
42224 app.zephyr.controller('commandController', app.directives.CommandController);
42225 app.zephyr.controller('optionController', app.directives.OptionController);
42226 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42227 app.zephyr.directive('command', app.directives.Command.Factory());
42228 app.zephyr.directive('option', app.directives.Option.Factory());
42229 })(app || (app = {}));
42234 /***/ function(module, exports) {
42239 (function (declares) {
42240 var CommandInfo = (function () {
42241 function CommandInfo(name) {
42244 return CommandInfo;
42246 declares.CommandInfo = CommandInfo;
42247 })(declares = app.declares || (app.declares = {}));
42248 })(app || (app = {}));
42252 (function (services) {
42253 var APIEndPoint = (function () {
42254 function APIEndPoint($resource) {
42255 this.$resource = $resource;
42257 APIEndPoint.prototype.resource = function () {
42262 var apiUrl = '/api/optionControlFile/dcdFilePrint';
42263 return this.$resource(apiUrl, {}, { getOption: getOption });
42265 APIEndPoint.prototype.getOptionControlFile = function () {
42266 return this.resource().query();
42268 return APIEndPoint;
42270 services.APIEndPoint = APIEndPoint;
42271 })(services = app.services || (app.services = {}));
42272 })(app || (app = {}));
42276 (function (services) {
42277 var MyModal = (function () {
42278 function MyModal($uibModal) {
42279 this.$uibModal = $uibModal;
42280 this.modalOption = {
42287 MyModal.prototype.open = function (modalName) {
42288 if (modalName === 'SelectCommand') {
42289 this.modalOption.templateUrl = 'templates/select-command.html';
42290 this.modalOption.size = 'lg';
42292 return this.$uibModal.open(this.modalOption);
42296 services.MyModal = MyModal;
42297 })(services = app.services || (app.services = {}));
42298 })(app || (app = {}));
42302 (function (directives) {
42303 var Command = (function () {
42304 function Command() {
42305 this.restrict = 'E';
42306 this.replace = true;
42308 this.controller = 'commandController';
42309 this.controllerAs = 'ctrl';
42310 this.bindToController = {
42316 this.templateUrl = 'templates/command.html';
42318 Command.Factory = function () {
42319 var directive = function () {
42320 return new Command();
42322 directive.$inject = [];
42327 directives.Command = Command;
42328 var CommandController = (function () {
42329 function CommandController(APIEndPoint, $scope) {
42330 this.APIEndPoint = APIEndPoint;
42331 this.$scope = $scope;
42332 var controller = this;
42334 .getOptionControlFile()
42336 .then(function (result) {
42337 controller.options = result;
42339 this.files = ['a.file', 'b.file', 'c.file'];
42340 this.heading = "[" + this.index + "]: dcdFilePring";
42341 this.isOpen = true;
42342 this.$scope.$on('close', function () {
42343 controller.isOpen = false;
42346 CommandController.prototype.submit = function () {
42348 angular.forEach(this.options, function (option) {
42350 angular.forEach(option.arg, function (arg) {
42352 inputs.push(arg.input);
42355 if (inputs.length > 0) {
42356 params[option.option] = inputs;
42359 console.log(params);
42361 CommandController.prototype.removeMySelf = function (index) {
42362 this.remove()(index, this.list);
42364 CommandController.$inject = ['APIEndPoint', '$scope'];
42365 return CommandController;
42367 directives.CommandController = CommandController;
42368 })(directives = app.directives || (app.directives = {}));
42369 })(app || (app = {}));
42373 (function (directives) {
42374 var HeaderMenu = (function () {
42375 function HeaderMenu() {
42376 this.restrict = 'E';
42377 this.replace = true;
42378 this.templateUrl = 'templates/header-menu.html';
42380 HeaderMenu.Factory = function () {
42381 var directive = function () {
42382 return new HeaderMenu();
42388 directives.HeaderMenu = HeaderMenu;
42389 })(directives = app.directives || (app.directives = {}));
42390 })(app || (app = {}));
42394 (function (directives) {
42395 var Option = (function () {
42396 function Option() {
42397 this.restrict = 'E';
42398 this.replace = true;
42399 this.controller = 'optionController';
42400 this.bindToController = {
42405 this.templateUrl = 'templates/option.html';
42406 this.controllerAs = 'ctrl';
42408 Option.Factory = function () {
42409 var directive = function () {
42410 return new Option();
42412 directive.$inject = [];
42417 directives.Option = Option;
42418 var OptionController = (function () {
42419 function OptionController() {
42420 var controller = this;
42421 angular.forEach(controller.info.arg, function (arg) {
42422 if (arg.initialValue) {
42423 if (arg.formType === 'number') {
42424 arg.input = parseInt(arg.initialValue);
42427 arg.input = arg.initialValue;
42432 OptionController.$inject = [];
42433 return OptionController;
42435 directives.OptionController = OptionController;
42436 })(directives = app.directives || (app.directives = {}));
42437 })(app || (app = {}));
42441 (function (controllers) {
42442 var Execution = (function () {
42443 function Execution(MyModal, $scope) {
42444 this.MyModal = MyModal;
42445 this.$scope = $scope;
42446 this.commandInfoList = [];
42449 Execution.prototype.add = function () {
42450 this.$scope.$broadcast('close');
42451 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
42453 Execution.prototype.open = function () {
42454 var result = this.MyModal.open('SelectCommand');
42455 console.log(result);
42457 Execution.prototype.remove = function (index, list) {
42458 list.splice(index, 1);
42460 Execution.prototype.close = function () {
42461 console.log("close");
42463 Execution.$inject = ['MyModal', '$scope'];
42466 controllers.Execution = Execution;
42467 })(controllers = app.controllers || (app.controllers = {}));
42468 })(app || (app = {}));
42472 (function (controllers) {
42473 var Files = (function () {
42474 function Files($scope) {
42475 this.page = "ManageFiles";
42477 Files.$inject = ['$scope'];
42480 controllers.Files = Files;
42481 })(controllers = app.controllers || (app.controllers = {}));
42482 })(app || (app = {}));
42486 (function (controllers) {
42487 var History = (function () {
42488 function History($scope) {
42489 this.page = "History";
42491 History.$inject = ['$scope'];
42494 controllers.History = History;
42495 })(controllers = app.controllers || (app.controllers = {}));
42496 })(app || (app = {}));
42500 var appName = 'zephyr';
42501 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42502 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42503 $urlRouterProvider.otherwise('/execution');
42504 $locationProvider.html5Mode({
42509 .state('execution', {
42511 templateUrl: 'templates/execution.html',
42512 controller: 'executionController',
42517 templateUrl: 'templates/files.html',
42518 controller: 'filesController',
42521 .state('history', {
42523 templateUrl: 'templates/history.html',
42524 controller: 'historyController',
42528 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42529 app.zephyr.service('MyModal', app.services.MyModal);
42530 app.zephyr.controller('executionController', app.controllers.Execution);
42531 app.zephyr.controller('filesController', app.controllers.Files);
42532 app.zephyr.controller('historyController', app.controllers.History);
42533 app.zephyr.controller('commandController', app.directives.CommandController);
42534 app.zephyr.controller('optionController', app.directives.OptionController);
42535 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42536 app.zephyr.directive('command', app.directives.Command.Factory());
42537 app.zephyr.directive('option', app.directives.Option.Factory());
42538 })(app || (app = {}));
42543 /***/ function(module, exports) {
42548 (function (declares) {
42549 var CommandInfo = (function () {
42550 function CommandInfo(name) {
42553 return CommandInfo;
42555 declares.CommandInfo = CommandInfo;
42556 })(declares = app.declares || (app.declares = {}));
42557 })(app || (app = {}));
42561 (function (services) {
42562 var APIEndPoint = (function () {
42563 function APIEndPoint($resource) {
42564 this.$resource = $resource;
42566 APIEndPoint.prototype.resource = function () {
42571 var apiUrl = '/api/optionControlFile/dcdFilePrint';
42572 return this.$resource(apiUrl, {}, { getOption: getOption });
42574 APIEndPoint.prototype.getOptionControlFile = function () {
42575 return this.resource().query();
42577 return APIEndPoint;
42579 services.APIEndPoint = APIEndPoint;
42580 })(services = app.services || (app.services = {}));
42581 })(app || (app = {}));
42585 (function (services) {
42586 var MyModal = (function () {
42587 function MyModal($uibModal) {
42588 this.$uibModal = $uibModal;
42589 this.modalOption = {
42596 MyModal.prototype.open = function (modalName) {
42597 if (modalName === 'SelectCommand') {
42598 this.modalOption.templateUrl = 'templates/select-command.html';
42599 this.modalOption.size = 'lg';
42601 return this.$uibModal.open(this.modalOption);
42605 services.MyModal = MyModal;
42606 })(services = app.services || (app.services = {}));
42607 })(app || (app = {}));
42611 (function (directives) {
42612 var Command = (function () {
42613 function Command() {
42614 this.restrict = 'E';
42615 this.replace = true;
42617 this.controller = 'commandController';
42618 this.controllerAs = 'ctrl';
42619 this.bindToController = {
42625 this.templateUrl = 'templates/command.html';
42627 Command.Factory = function () {
42628 var directive = function () {
42629 return new Command();
42631 directive.$inject = [];
42636 directives.Command = Command;
42637 var CommandController = (function () {
42638 function CommandController(APIEndPoint, $scope) {
42639 this.APIEndPoint = APIEndPoint;
42640 this.$scope = $scope;
42641 var controller = this;
42643 .getOptionControlFile()
42645 .then(function (result) {
42646 controller.options = result;
42648 this.files = ['a.file', 'b.file', 'c.file'];
42649 this.heading = "[" + this.index + "]: dcdFilePring";
42650 this.isOpen = true;
42651 this.$scope.$on('close', function () {
42652 controller.isOpen = false;
42655 CommandController.prototype.submit = function () {
42657 angular.forEach(this.options, function (option) {
42659 angular.forEach(option.arg, function (arg) {
42661 inputs.push(arg.input);
42664 if (inputs.length > 0) {
42665 params[option.option] = inputs;
42668 console.log(params);
42670 CommandController.prototype.removeMySelf = function (index) {
42671 this.remove()(index, this.list);
42673 CommandController.$inject = ['APIEndPoint', '$scope'];
42674 return CommandController;
42676 directives.CommandController = CommandController;
42677 })(directives = app.directives || (app.directives = {}));
42678 })(app || (app = {}));
42682 (function (directives) {
42683 var HeaderMenu = (function () {
42684 function HeaderMenu() {
42685 this.restrict = 'E';
42686 this.replace = true;
42687 this.templateUrl = 'templates/header-menu.html';
42689 HeaderMenu.Factory = function () {
42690 var directive = function () {
42691 return new HeaderMenu();
42697 directives.HeaderMenu = HeaderMenu;
42698 })(directives = app.directives || (app.directives = {}));
42699 })(app || (app = {}));
42703 (function (directives) {
42704 var Option = (function () {
42705 function Option() {
42706 this.restrict = 'E';
42707 this.replace = true;
42708 this.controller = 'optionController';
42709 this.bindToController = {
42714 this.templateUrl = 'templates/option.html';
42715 this.controllerAs = 'ctrl';
42717 Option.Factory = function () {
42718 var directive = function () {
42719 return new Option();
42721 directive.$inject = [];
42726 directives.Option = Option;
42727 var OptionController = (function () {
42728 function OptionController() {
42729 var controller = this;
42730 angular.forEach(controller.info.arg, function (arg) {
42731 if (arg.initialValue) {
42732 if (arg.formType === 'number') {
42733 arg.input = parseInt(arg.initialValue);
42736 arg.input = arg.initialValue;
42741 OptionController.$inject = [];
42742 return OptionController;
42744 directives.OptionController = OptionController;
42745 })(directives = app.directives || (app.directives = {}));
42746 })(app || (app = {}));
42750 (function (controllers) {
42751 var Execution = (function () {
42752 function Execution(MyModal, $scope) {
42753 this.MyModal = MyModal;
42754 this.$scope = $scope;
42755 this.commandInfoList = [];
42758 Execution.prototype.add = function () {
42759 this.$scope.$broadcast('close');
42760 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
42762 Execution.prototype.open = function () {
42763 var result = this.MyModal.open('SelectCommand');
42764 console.log(result);
42766 Execution.prototype.remove = function (index, list) {
42767 list.splice(index, 1);
42769 Execution.prototype.close = function () {
42770 console.log("close");
42772 Execution.$inject = ['MyModal', '$scope'];
42775 controllers.Execution = Execution;
42776 })(controllers = app.controllers || (app.controllers = {}));
42777 })(app || (app = {}));
42781 (function (controllers) {
42782 var Files = (function () {
42783 function Files($scope) {
42784 this.page = "ManageFiles";
42786 Files.$inject = ['$scope'];
42789 controllers.Files = Files;
42790 })(controllers = app.controllers || (app.controllers = {}));
42791 })(app || (app = {}));
42795 (function (controllers) {
42796 var History = (function () {
42797 function History($scope) {
42798 this.page = "History";
42800 History.$inject = ['$scope'];
42803 controllers.History = History;
42804 })(controllers = app.controllers || (app.controllers = {}));
42805 })(app || (app = {}));
42809 var appName = 'zephyr';
42810 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42811 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42812 $urlRouterProvider.otherwise('/execution');
42813 $locationProvider.html5Mode({
42818 .state('execution', {
42820 templateUrl: 'templates/execution.html',
42821 controller: 'executionController',
42826 templateUrl: 'templates/files.html',
42827 controller: 'filesController',
42830 .state('history', {
42832 templateUrl: 'templates/history.html',
42833 controller: 'historyController',
42837 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42838 app.zephyr.service('MyModal', app.services.MyModal);
42839 app.zephyr.controller('executionController', app.controllers.Execution);
42840 app.zephyr.controller('filesController', app.controllers.Files);
42841 app.zephyr.controller('historyController', app.controllers.History);
42842 app.zephyr.controller('commandController', app.directives.CommandController);
42843 app.zephyr.controller('optionController', app.directives.OptionController);
42844 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42845 app.zephyr.directive('command', app.directives.Command.Factory());
42846 app.zephyr.directive('option', app.directives.Option.Factory());
42847 })(app || (app = {}));
42852 /***/ function(module, exports) {
42857 (function (declares) {
42858 var CommandInfo = (function () {
42859 function CommandInfo(name) {
42862 return CommandInfo;
42864 declares.CommandInfo = CommandInfo;
42865 })(declares = app.declares || (app.declares = {}));
42866 })(app || (app = {}));
42870 (function (services) {
42871 var APIEndPoint = (function () {
42872 function APIEndPoint($resource) {
42873 this.$resource = $resource;
42875 APIEndPoint.prototype.resource = function () {
42880 var apiUrl = '/api/optionControlFile/dcdFilePrint';
42881 return this.$resource(apiUrl, {}, { getOption: getOption });
42883 APIEndPoint.prototype.getOptionControlFile = function () {
42884 return this.resource().query();
42886 return APIEndPoint;
42888 services.APIEndPoint = APIEndPoint;
42889 })(services = app.services || (app.services = {}));
42890 })(app || (app = {}));
42894 (function (services) {
42895 var MyModal = (function () {
42896 function MyModal($uibModal) {
42897 this.$uibModal = $uibModal;
42898 this.modalOption = {
42905 MyModal.prototype.open = function (modalName) {
42906 if (modalName === 'SelectCommand') {
42907 this.modalOption.templateUrl = 'templates/select-command.html';
42908 this.modalOption.size = 'lg';
42910 return this.$uibModal.open(this.modalOption);
42914 services.MyModal = MyModal;
42915 })(services = app.services || (app.services = {}));
42916 })(app || (app = {}));
42920 (function (directives) {
42921 var Command = (function () {
42922 function Command() {
42923 this.restrict = 'E';
42924 this.replace = true;
42926 this.controller = 'commandController';
42927 this.controllerAs = 'ctrl';
42928 this.bindToController = {
42934 this.templateUrl = 'templates/command.html';
42936 Command.Factory = function () {
42937 var directive = function () {
42938 return new Command();
42940 directive.$inject = [];
42945 directives.Command = Command;
42946 var CommandController = (function () {
42947 function CommandController(APIEndPoint, $scope) {
42948 this.APIEndPoint = APIEndPoint;
42949 this.$scope = $scope;
42950 var controller = this;
42952 .getOptionControlFile()
42954 .then(function (result) {
42955 controller.options = result;
42957 this.files = ['a.file', 'b.file', 'c.file'];
42958 this.heading = "[" + this.index + "]: dcdFilePring";
42959 this.isOpen = true;
42960 this.$scope.$on('close', function () {
42961 controller.isOpen = false;
42964 CommandController.prototype.submit = function () {
42966 angular.forEach(this.options, function (option) {
42968 angular.forEach(option.arg, function (arg) {
42970 inputs.push(arg.input);
42973 if (inputs.length > 0) {
42974 params[option.option] = inputs;
42977 console.log(params);
42979 CommandController.prototype.removeMySelf = function (index) {
42980 this.remove()(index, this.list);
42982 CommandController.$inject = ['APIEndPoint', '$scope'];
42983 return CommandController;
42985 directives.CommandController = CommandController;
42986 })(directives = app.directives || (app.directives = {}));
42987 })(app || (app = {}));
42991 (function (directives) {
42992 var HeaderMenu = (function () {
42993 function HeaderMenu() {
42994 this.restrict = 'E';
42995 this.replace = true;
42996 this.templateUrl = 'templates/header-menu.html';
42998 HeaderMenu.Factory = function () {
42999 var directive = function () {
43000 return new HeaderMenu();
43006 directives.HeaderMenu = HeaderMenu;
43007 })(directives = app.directives || (app.directives = {}));
43008 })(app || (app = {}));
43012 (function (directives) {
43013 var Option = (function () {
43014 function Option() {
43015 this.restrict = 'E';
43016 this.replace = true;
43017 this.controller = 'optionController';
43018 this.bindToController = {
43023 this.templateUrl = 'templates/option.html';
43024 this.controllerAs = 'ctrl';
43026 Option.Factory = function () {
43027 var directive = function () {
43028 return new Option();
43030 directive.$inject = [];
43035 directives.Option = Option;
43036 var OptionController = (function () {
43037 function OptionController() {
43038 var controller = this;
43039 angular.forEach(controller.info.arg, function (arg) {
43040 if (arg.initialValue) {
43041 if (arg.formType === 'number') {
43042 arg.input = parseInt(arg.initialValue);
43045 arg.input = arg.initialValue;
43050 OptionController.$inject = [];
43051 return OptionController;
43053 directives.OptionController = OptionController;
43054 })(directives = app.directives || (app.directives = {}));
43055 })(app || (app = {}));
43059 (function (controllers) {
43060 var Execution = (function () {
43061 function Execution(MyModal, $scope) {
43062 this.MyModal = MyModal;
43063 this.$scope = $scope;
43064 this.commandInfoList = [];
43067 Execution.prototype.add = function () {
43068 this.$scope.$broadcast('close');
43069 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
43071 Execution.prototype.open = function () {
43072 var result = this.MyModal.open('SelectCommand');
43073 console.log(result);
43075 Execution.prototype.remove = function (index, list) {
43076 list.splice(index, 1);
43078 Execution.prototype.close = function () {
43079 console.log("close");
43081 Execution.$inject = ['MyModal', '$scope'];
43084 controllers.Execution = Execution;
43085 })(controllers = app.controllers || (app.controllers = {}));
43086 })(app || (app = {}));
43090 (function (controllers) {
43091 var Files = (function () {
43092 function Files($scope) {
43093 this.page = "ManageFiles";
43095 Files.$inject = ['$scope'];
43098 controllers.Files = Files;
43099 })(controllers = app.controllers || (app.controllers = {}));
43100 })(app || (app = {}));
43104 (function (controllers) {
43105 var History = (function () {
43106 function History($scope) {
43107 this.page = "History";
43109 History.$inject = ['$scope'];
43112 controllers.History = History;
43113 })(controllers = app.controllers || (app.controllers = {}));
43114 })(app || (app = {}));
43118 var appName = 'zephyr';
43119 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43120 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43121 $urlRouterProvider.otherwise('/execution');
43122 $locationProvider.html5Mode({
43127 .state('execution', {
43129 templateUrl: 'templates/execution.html',
43130 controller: 'executionController',
43135 templateUrl: 'templates/files.html',
43136 controller: 'filesController',
43139 .state('history', {
43141 templateUrl: 'templates/history.html',
43142 controller: 'historyController',
43146 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43147 app.zephyr.service('MyModal', app.services.MyModal);
43148 app.zephyr.controller('executionController', app.controllers.Execution);
43149 app.zephyr.controller('filesController', app.controllers.Files);
43150 app.zephyr.controller('historyController', app.controllers.History);
43151 app.zephyr.controller('commandController', app.directives.CommandController);
43152 app.zephyr.controller('optionController', app.directives.OptionController);
43153 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43154 app.zephyr.directive('command', app.directives.Command.Factory());
43155 app.zephyr.directive('option', app.directives.Option.Factory());
43156 })(app || (app = {}));
43161 /***/ function(module, exports) {
43166 (function (declares) {
43167 var CommandInfo = (function () {
43168 function CommandInfo(name) {
43171 return CommandInfo;
43173 declares.CommandInfo = CommandInfo;
43174 })(declares = app.declares || (app.declares = {}));
43175 })(app || (app = {}));
43179 (function (services) {
43180 var APIEndPoint = (function () {
43181 function APIEndPoint($resource) {
43182 this.$resource = $resource;
43184 APIEndPoint.prototype.resource = function () {
43189 var apiUrl = '/api/optionControlFile/dcdFilePrint';
43190 return this.$resource(apiUrl, {}, { getOption: getOption });
43192 APIEndPoint.prototype.getOptionControlFile = function () {
43193 return this.resource().query();
43195 return APIEndPoint;
43197 services.APIEndPoint = APIEndPoint;
43198 })(services = app.services || (app.services = {}));
43199 })(app || (app = {}));
43203 (function (services) {
43204 var MyModal = (function () {
43205 function MyModal($uibModal) {
43206 this.$uibModal = $uibModal;
43207 this.modalOption = {
43214 MyModal.prototype.open = function (modalName) {
43215 if (modalName === 'SelectCommand') {
43216 this.modalOption.templateUrl = 'templates/select-command.html';
43217 this.modalOption.size = 'lg';
43219 return this.$uibModal.open(this.modalOption);
43223 services.MyModal = MyModal;
43224 })(services = app.services || (app.services = {}));
43225 })(app || (app = {}));
43229 (function (directives) {
43230 var Command = (function () {
43231 function Command() {
43232 this.restrict = 'E';
43233 this.replace = true;
43235 this.controller = 'commandController';
43236 this.controllerAs = 'ctrl';
43237 this.bindToController = {
43243 this.templateUrl = 'templates/command.html';
43245 Command.Factory = function () {
43246 var directive = function () {
43247 return new Command();
43249 directive.$inject = [];
43254 directives.Command = Command;
43255 var CommandController = (function () {
43256 function CommandController(APIEndPoint, $scope) {
43257 this.APIEndPoint = APIEndPoint;
43258 this.$scope = $scope;
43259 var controller = this;
43261 .getOptionControlFile()
43263 .then(function (result) {
43264 controller.options = result;
43266 this.files = ['a.file', 'b.file', 'c.file'];
43267 this.heading = "[" + this.index + "]: dcdFilePring";
43268 this.isOpen = true;
43269 this.$scope.$on('close', function () {
43270 controller.isOpen = false;
43273 CommandController.prototype.submit = function () {
43275 angular.forEach(this.options, function (option) {
43277 angular.forEach(option.arg, function (arg) {
43279 inputs.push(arg.input);
43282 if (inputs.length > 0) {
43283 params[option.option] = inputs;
43286 console.log(params);
43288 CommandController.prototype.removeMySelf = function (index) {
43289 this.remove()(index, this.list);
43291 CommandController.$inject = ['APIEndPoint', '$scope'];
43292 return CommandController;
43294 directives.CommandController = CommandController;
43295 })(directives = app.directives || (app.directives = {}));
43296 })(app || (app = {}));
43300 (function (directives) {
43301 var HeaderMenu = (function () {
43302 function HeaderMenu() {
43303 this.restrict = 'E';
43304 this.replace = true;
43305 this.templateUrl = 'templates/header-menu.html';
43307 HeaderMenu.Factory = function () {
43308 var directive = function () {
43309 return new HeaderMenu();
43315 directives.HeaderMenu = HeaderMenu;
43316 })(directives = app.directives || (app.directives = {}));
43317 })(app || (app = {}));
43321 (function (directives) {
43322 var Option = (function () {
43323 function Option() {
43324 this.restrict = 'E';
43325 this.replace = true;
43326 this.controller = 'optionController';
43327 this.bindToController = {
43332 this.templateUrl = 'templates/option.html';
43333 this.controllerAs = 'ctrl';
43335 Option.Factory = function () {
43336 var directive = function () {
43337 return new Option();
43339 directive.$inject = [];
43344 directives.Option = Option;
43345 var OptionController = (function () {
43346 function OptionController() {
43347 var controller = this;
43348 angular.forEach(controller.info.arg, function (arg) {
43349 if (arg.initialValue) {
43350 if (arg.formType === 'number') {
43351 arg.input = parseInt(arg.initialValue);
43354 arg.input = arg.initialValue;
43359 OptionController.$inject = [];
43360 return OptionController;
43362 directives.OptionController = OptionController;
43363 })(directives = app.directives || (app.directives = {}));
43364 })(app || (app = {}));
43368 (function (controllers) {
43369 var Execution = (function () {
43370 function Execution(MyModal, $scope) {
43371 this.MyModal = MyModal;
43372 this.$scope = $scope;
43373 this.commandInfoList = [];
43376 Execution.prototype.add = function () {
43377 this.$scope.$broadcast('close');
43378 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
43380 Execution.prototype.open = function () {
43381 var result = this.MyModal.open('SelectCommand');
43382 console.log(result);
43384 Execution.prototype.remove = function (index, list) {
43385 list.splice(index, 1);
43387 Execution.prototype.close = function () {
43388 console.log("close");
43390 Execution.$inject = ['MyModal', '$scope'];
43393 controllers.Execution = Execution;
43394 })(controllers = app.controllers || (app.controllers = {}));
43395 })(app || (app = {}));
43399 (function (controllers) {
43400 var Files = (function () {
43401 function Files($scope) {
43402 this.page = "ManageFiles";
43404 Files.$inject = ['$scope'];
43407 controllers.Files = Files;
43408 })(controllers = app.controllers || (app.controllers = {}));
43409 })(app || (app = {}));
43413 (function (controllers) {
43414 var History = (function () {
43415 function History($scope) {
43416 this.page = "History";
43418 History.$inject = ['$scope'];
43421 controllers.History = History;
43422 })(controllers = app.controllers || (app.controllers = {}));
43423 })(app || (app = {}));
43427 var appName = 'zephyr';
43428 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43429 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43430 $urlRouterProvider.otherwise('/execution');
43431 $locationProvider.html5Mode({
43436 .state('execution', {
43438 templateUrl: 'templates/execution.html',
43439 controller: 'executionController',
43444 templateUrl: 'templates/files.html',
43445 controller: 'filesController',
43448 .state('history', {
43450 templateUrl: 'templates/history.html',
43451 controller: 'historyController',
43455 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43456 app.zephyr.service('MyModal', app.services.MyModal);
43457 app.zephyr.controller('executionController', app.controllers.Execution);
43458 app.zephyr.controller('filesController', app.controllers.Files);
43459 app.zephyr.controller('historyController', app.controllers.History);
43460 app.zephyr.controller('commandController', app.directives.CommandController);
43461 app.zephyr.controller('optionController', app.directives.OptionController);
43462 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43463 app.zephyr.directive('command', app.directives.Command.Factory());
43464 app.zephyr.directive('option', app.directives.Option.Factory());
43465 })(app || (app = {}));
43470 /***/ function(module, exports) {
43475 (function (declares) {
43476 var CommandInfo = (function () {
43477 function CommandInfo(name) {
43480 return CommandInfo;
43482 declares.CommandInfo = CommandInfo;
43483 })(declares = app.declares || (app.declares = {}));
43484 })(app || (app = {}));
43488 (function (services) {
43489 var APIEndPoint = (function () {
43490 function APIEndPoint($resource) {
43491 this.$resource = $resource;
43493 APIEndPoint.prototype.resource = function () {
43498 var apiUrl = '/api/optionControlFile/dcdFilePrint';
43499 return this.$resource(apiUrl, {}, { getOption: getOption });
43501 APIEndPoint.prototype.getOptionControlFile = function () {
43502 return this.resource().query();
43504 return APIEndPoint;
43506 services.APIEndPoint = APIEndPoint;
43507 })(services = app.services || (app.services = {}));
43508 })(app || (app = {}));
43512 (function (services) {
43513 var MyModal = (function () {
43514 function MyModal($uibModal) {
43515 this.$uibModal = $uibModal;
43516 this.modalOption = {
43523 MyModal.prototype.open = function (modalName) {
43524 if (modalName === 'SelectCommand') {
43525 this.modalOption.templateUrl = 'templates/select-command.html';
43526 this.modalOption.size = 'lg';
43528 return this.$uibModal.open(this.modalOption);
43532 services.MyModal = MyModal;
43533 })(services = app.services || (app.services = {}));
43534 })(app || (app = {}));
43538 (function (directives) {
43539 var Command = (function () {
43540 function Command() {
43541 this.restrict = 'E';
43542 this.replace = true;
43544 this.controller = 'commandController';
43545 this.controllerAs = 'ctrl';
43546 this.bindToController = {
43552 this.templateUrl = 'templates/command.html';
43554 Command.Factory = function () {
43555 var directive = function () {
43556 return new Command();
43558 directive.$inject = [];
43563 directives.Command = Command;
43564 var CommandController = (function () {
43565 function CommandController(APIEndPoint, $scope) {
43566 this.APIEndPoint = APIEndPoint;
43567 this.$scope = $scope;
43568 var controller = this;
43570 .getOptionControlFile()
43572 .then(function (result) {
43573 controller.options = result;
43575 this.files = ['a.file', 'b.file', 'c.file'];
43576 this.heading = "[" + this.index + "]: dcdFilePring";
43577 this.isOpen = true;
43578 this.$scope.$on('close', function () {
43579 controller.isOpen = false;
43582 CommandController.prototype.submit = function () {
43584 angular.forEach(this.options, function (option) {
43586 angular.forEach(option.arg, function (arg) {
43588 inputs.push(arg.input);
43591 if (inputs.length > 0) {
43592 params[option.option] = inputs;
43595 console.log(params);
43597 CommandController.prototype.removeMySelf = function (index) {
43598 this.remove()(index, this.list);
43600 CommandController.$inject = ['APIEndPoint', '$scope'];
43601 return CommandController;
43603 directives.CommandController = CommandController;
43604 })(directives = app.directives || (app.directives = {}));
43605 })(app || (app = {}));
43609 (function (directives) {
43610 var HeaderMenu = (function () {
43611 function HeaderMenu() {
43612 this.restrict = 'E';
43613 this.replace = true;
43614 this.templateUrl = 'templates/header-menu.html';
43616 HeaderMenu.Factory = function () {
43617 var directive = function () {
43618 return new HeaderMenu();
43624 directives.HeaderMenu = HeaderMenu;
43625 })(directives = app.directives || (app.directives = {}));
43626 })(app || (app = {}));
43630 (function (directives) {
43631 var Option = (function () {
43632 function Option() {
43633 this.restrict = 'E';
43634 this.replace = true;
43635 this.controller = 'optionController';
43636 this.bindToController = {
43641 this.templateUrl = 'templates/option.html';
43642 this.controllerAs = 'ctrl';
43644 Option.Factory = function () {
43645 var directive = function () {
43646 return new Option();
43648 directive.$inject = [];
43653 directives.Option = Option;
43654 var OptionController = (function () {
43655 function OptionController() {
43656 var controller = this;
43657 angular.forEach(controller.info.arg, function (arg) {
43658 if (arg.initialValue) {
43659 if (arg.formType === 'number') {
43660 arg.input = parseInt(arg.initialValue);
43663 arg.input = arg.initialValue;
43668 OptionController.$inject = [];
43669 return OptionController;
43671 directives.OptionController = OptionController;
43672 })(directives = app.directives || (app.directives = {}));
43673 })(app || (app = {}));
43677 (function (controllers) {
43678 var Execution = (function () {
43679 function Execution(MyModal, $scope) {
43680 this.MyModal = MyModal;
43681 this.$scope = $scope;
43682 this.commandInfoList = [];
43685 Execution.prototype.add = function () {
43686 this.$scope.$broadcast('close');
43687 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
43689 Execution.prototype.open = function () {
43690 var result = this.MyModal.open('SelectCommand');
43691 console.log(result);
43693 Execution.prototype.remove = function (index, list) {
43694 list.splice(index, 1);
43696 Execution.prototype.close = function () {
43697 console.log("close");
43699 Execution.$inject = ['MyModal', '$scope'];
43702 controllers.Execution = Execution;
43703 })(controllers = app.controllers || (app.controllers = {}));
43704 })(app || (app = {}));
43708 (function (controllers) {
43709 var Files = (function () {
43710 function Files($scope) {
43711 this.page = "ManageFiles";
43713 Files.$inject = ['$scope'];
43716 controllers.Files = Files;
43717 })(controllers = app.controllers || (app.controllers = {}));
43718 })(app || (app = {}));
43722 (function (controllers) {
43723 var History = (function () {
43724 function History($scope) {
43725 this.page = "History";
43727 History.$inject = ['$scope'];
43730 controllers.History = History;
43731 })(controllers = app.controllers || (app.controllers = {}));
43732 })(app || (app = {}));
43736 var appName = 'zephyr';
43737 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43738 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43739 $urlRouterProvider.otherwise('/execution');
43740 $locationProvider.html5Mode({
43745 .state('execution', {
43747 templateUrl: 'templates/execution.html',
43748 controller: 'executionController',
43753 templateUrl: 'templates/files.html',
43754 controller: 'filesController',
43757 .state('history', {
43759 templateUrl: 'templates/history.html',
43760 controller: 'historyController',
43764 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43765 app.zephyr.service('MyModal', app.services.MyModal);
43766 app.zephyr.controller('executionController', app.controllers.Execution);
43767 app.zephyr.controller('filesController', app.controllers.Files);
43768 app.zephyr.controller('historyController', app.controllers.History);
43769 app.zephyr.controller('commandController', app.directives.CommandController);
43770 app.zephyr.controller('optionController', app.directives.OptionController);
43771 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43772 app.zephyr.directive('command', app.directives.Command.Factory());
43773 app.zephyr.directive('option', app.directives.Option.Factory());
43774 })(app || (app = {}));
43779 /***/ function(module, exports) {
43784 (function (declares) {
43785 var CommandInfo = (function () {
43786 function CommandInfo(name) {
43789 return CommandInfo;
43791 declares.CommandInfo = CommandInfo;
43792 })(declares = app.declares || (app.declares = {}));
43793 })(app || (app = {}));
43797 (function (services) {
43798 var APIEndPoint = (function () {
43799 function APIEndPoint($resource) {
43800 this.$resource = $resource;
43802 APIEndPoint.prototype.resource = function () {
43807 var apiUrl = '/api/optionControlFile/dcdFilePrint';
43808 return this.$resource(apiUrl, {}, { getOption: getOption });
43810 APIEndPoint.prototype.getOptionControlFile = function () {
43811 return this.resource().query();
43813 return APIEndPoint;
43815 services.APIEndPoint = APIEndPoint;
43816 })(services = app.services || (app.services = {}));
43817 })(app || (app = {}));
43821 (function (services) {
43822 var MyModal = (function () {
43823 function MyModal($uibModal) {
43824 this.$uibModal = $uibModal;
43825 this.modalOption = {
43832 MyModal.prototype.open = function (modalName) {
43833 if (modalName === 'SelectCommand') {
43834 this.modalOption.templateUrl = 'templates/select-command.html';
43835 this.modalOption.size = 'lg';
43837 return this.$uibModal.open(this.modalOption);
43841 services.MyModal = MyModal;
43842 })(services = app.services || (app.services = {}));
43843 })(app || (app = {}));
43847 (function (directives) {
43848 var Command = (function () {
43849 function Command() {
43850 this.restrict = 'E';
43851 this.replace = true;
43853 this.controller = 'commandController';
43854 this.controllerAs = 'ctrl';
43855 this.bindToController = {
43861 this.templateUrl = 'templates/command.html';
43863 Command.Factory = function () {
43864 var directive = function () {
43865 return new Command();
43867 directive.$inject = [];
43872 directives.Command = Command;
43873 var CommandController = (function () {
43874 function CommandController(APIEndPoint, $scope) {
43875 this.APIEndPoint = APIEndPoint;
43876 this.$scope = $scope;
43877 var controller = this;
43879 .getOptionControlFile()
43881 .then(function (result) {
43882 controller.options = result;
43884 this.files = ['a.file', 'b.file', 'c.file'];
43885 this.heading = "[" + this.index + "]: dcdFilePring";
43886 this.isOpen = true;
43887 this.$scope.$on('close', function () {
43888 controller.isOpen = false;
43891 CommandController.prototype.submit = function () {
43893 angular.forEach(this.options, function (option) {
43895 angular.forEach(option.arg, function (arg) {
43897 inputs.push(arg.input);
43900 if (inputs.length > 0) {
43901 params[option.option] = inputs;
43904 console.log(params);
43906 CommandController.prototype.removeMySelf = function (index) {
43907 this.remove()(index, this.list);
43909 CommandController.$inject = ['APIEndPoint', '$scope'];
43910 return CommandController;
43912 directives.CommandController = CommandController;
43913 })(directives = app.directives || (app.directives = {}));
43914 })(app || (app = {}));
43918 (function (directives) {
43919 var HeaderMenu = (function () {
43920 function HeaderMenu() {
43921 this.restrict = 'E';
43922 this.replace = true;
43923 this.templateUrl = 'templates/header-menu.html';
43925 HeaderMenu.Factory = function () {
43926 var directive = function () {
43927 return new HeaderMenu();
43933 directives.HeaderMenu = HeaderMenu;
43934 })(directives = app.directives || (app.directives = {}));
43935 })(app || (app = {}));
43939 (function (directives) {
43940 var Option = (function () {
43941 function Option() {
43942 this.restrict = 'E';
43943 this.replace = true;
43944 this.controller = 'optionController';
43945 this.bindToController = {
43950 this.templateUrl = 'templates/option.html';
43951 this.controllerAs = 'ctrl';
43953 Option.Factory = function () {
43954 var directive = function () {
43955 return new Option();
43957 directive.$inject = [];
43962 directives.Option = Option;
43963 var OptionController = (function () {
43964 function OptionController() {
43965 var controller = this;
43966 angular.forEach(controller.info.arg, function (arg) {
43967 if (arg.initialValue) {
43968 if (arg.formType === 'number') {
43969 arg.input = parseInt(arg.initialValue);
43972 arg.input = arg.initialValue;
43977 OptionController.$inject = [];
43978 return OptionController;
43980 directives.OptionController = OptionController;
43981 })(directives = app.directives || (app.directives = {}));
43982 })(app || (app = {}));
43986 (function (controllers) {
43987 var Execution = (function () {
43988 function Execution(MyModal, $scope) {
43989 this.MyModal = MyModal;
43990 this.$scope = $scope;
43991 this.commandInfoList = [];
43994 Execution.prototype.add = function () {
43995 this.$scope.$broadcast('close');
43996 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
43998 Execution.prototype.open = function () {
43999 var result = this.MyModal.open('SelectCommand');
44000 console.log(result);
44002 Execution.prototype.remove = function (index, list) {
44003 list.splice(index, 1);
44005 Execution.prototype.close = function () {
44006 console.log("close");
44008 Execution.$inject = ['MyModal', '$scope'];
44011 controllers.Execution = Execution;
44012 })(controllers = app.controllers || (app.controllers = {}));
44013 })(app || (app = {}));
44017 (function (controllers) {
44018 var Files = (function () {
44019 function Files($scope) {
44020 this.page = "ManageFiles";
44022 Files.$inject = ['$scope'];
44025 controllers.Files = Files;
44026 })(controllers = app.controllers || (app.controllers = {}));
44027 })(app || (app = {}));
44031 (function (controllers) {
44032 var History = (function () {
44033 function History($scope) {
44034 this.page = "History";
44036 History.$inject = ['$scope'];
44039 controllers.History = History;
44040 })(controllers = app.controllers || (app.controllers = {}));
44041 })(app || (app = {}));
44045 var appName = 'zephyr';
44046 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44047 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44048 $urlRouterProvider.otherwise('/execution');
44049 $locationProvider.html5Mode({
44054 .state('execution', {
44056 templateUrl: 'templates/execution.html',
44057 controller: 'executionController',
44062 templateUrl: 'templates/files.html',
44063 controller: 'filesController',
44066 .state('history', {
44068 templateUrl: 'templates/history.html',
44069 controller: 'historyController',
44073 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44074 app.zephyr.service('MyModal', app.services.MyModal);
44075 app.zephyr.controller('executionController', app.controllers.Execution);
44076 app.zephyr.controller('filesController', app.controllers.Files);
44077 app.zephyr.controller('historyController', app.controllers.History);
44078 app.zephyr.controller('commandController', app.directives.CommandController);
44079 app.zephyr.controller('optionController', app.directives.OptionController);
44080 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44081 app.zephyr.directive('command', app.directives.Command.Factory());
44082 app.zephyr.directive('option', app.directives.Option.Factory());
44083 })(app || (app = {}));